mirror of
https://github.com/imfing/hextra.git
synced 2025-09-15 12:01:59 -04:00
feat(image-zoom): add minimal image zoom functionality
- Introduced CSS for image zoom overlay and image styling. - Implemented JavaScript for handling image zoom interactions, including overlay creation and close functionality. - Updated configuration to enable image zoom feature in site parameters. - Added partial for including image zoom assets in the layout.
This commit is contained in:
42
assets/css/components/image-zoom.css
Normal file
42
assets/css/components/image-zoom.css
Normal file
@@ -0,0 +1,42 @@
|
||||
/* Minimal styles for Hextra image zoom overlay */
|
||||
.hextra-zoom-image-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--hextra-image-zoom-backdrop, rgba(0, 0, 0, 0.9));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease-out;
|
||||
cursor: zoom-out;
|
||||
}
|
||||
|
||||
.hextra-zoom-image-overlay.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.hextra-zoom-image {
|
||||
max-width: min(95vw, 1200px);
|
||||
max-height: 95vh;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.35);
|
||||
transition: transform 0.3s ease-out;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.hextra-zoom-image-overlay.show .hextra-zoom-image {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hextra-zoom-image-overlay,
|
||||
.hextra-zoom-image {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show magnifier cursor over zoomable images in content */
|
||||
.content img:not([data-no-zoom]) {
|
||||
cursor: zoom-in;
|
||||
}
|
76
assets/js/image-zoom.js
Normal file
76
assets/js/image-zoom.js
Normal file
@@ -0,0 +1,76 @@
|
||||
// Minimal, dependency-free image zoom for Hextra
|
||||
// - Activates on images inside `.content`
|
||||
// - Close on overlay click or Escape
|
||||
// - Opt-out with `data-no-zoom` on <img>
|
||||
|
||||
(function () {
|
||||
function ready(fn) {
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", fn, { once: true });
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
function createOverlay(src, alt) {
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "hextra-zoom-image-overlay";
|
||||
overlay.setAttribute("role", "dialog");
|
||||
overlay.setAttribute("aria-modal", "true");
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.className = "hextra-zoom-image";
|
||||
img.src = src;
|
||||
if (alt) img.alt = alt;
|
||||
|
||||
overlay.appendChild(img);
|
||||
|
||||
function close() {
|
||||
overlay.classList.remove("show");
|
||||
document.documentElement.style.removeProperty("overflow");
|
||||
window.removeEventListener("keydown", onKeyDown, true);
|
||||
overlay.addEventListener(
|
||||
"transitionend",
|
||||
() => overlay.remove(),
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
|
||||
function onKeyDown(e) {
|
||||
if (e.key === "Escape") close();
|
||||
}
|
||||
|
||||
overlay.addEventListener("click", close, { once: true });
|
||||
window.addEventListener("keydown", onKeyDown, true);
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
// lock scroll
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
|
||||
// trigger fade-in
|
||||
requestAnimationFrame(() => overlay.classList.add("show"));
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
const container = document.querySelector(".content");
|
||||
if (!container) return;
|
||||
|
||||
container.addEventListener(
|
||||
"click",
|
||||
function (e) {
|
||||
const target = e.target;
|
||||
if (!(target instanceof HTMLImageElement)) return;
|
||||
if (target.dataset.noZoom === "" || target.dataset.noZoom === "true") return;
|
||||
|
||||
// avoid following parent links when zooming
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const src = target.currentSrc || target.src;
|
||||
if (!src) return;
|
||||
createOverlay(src, target.alt || "");
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
})();
|
@@ -238,3 +238,6 @@ params:
|
||||
# inputPosition: top
|
||||
# lang: en
|
||||
# theme: noborder_dark
|
||||
|
||||
imageZoom:
|
||||
enable: true
|
||||
|
@@ -13,3 +13,10 @@
|
||||
{{- if (.Store.Get "hasAsciinema") -}}
|
||||
{{- partial "scripts/asciinema.html" . -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/* Image zoom */}}
|
||||
{{- with site.Params.imageZoom }}
|
||||
{{- if .enable }}
|
||||
{{- partial "scripts/image-zoom.html" $ -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
13
layouts/_partials/scripts/image-zoom.html
Normal file
13
layouts/_partials/scripts/image-zoom.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{{/* Optional minimal image zoom assets */}}
|
||||
{{- $js := resources.Get "js/image-zoom.js" -}}
|
||||
{{- $css := resources.Get "css/components/image-zoom.css" -}}
|
||||
|
||||
{{- if hugo.IsProduction -}}
|
||||
{{- $js = $js | minify | fingerprint -}}
|
||||
{{- $css = $css | minify | fingerprint -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
<link rel="preload" href="{{ $css.RelPermalink }}" as="style" integrity="{{ $css.Data.Integrity }}" />
|
||||
<link href="{{ $css.RelPermalink }}" rel="stylesheet" integrity="{{ $css.Data.Integrity }}" />
|
||||
<script defer src="{{ $js.RelPermalink }}" integrity="{{ $js.Data.Integrity }}"></script>
|
Reference in New Issue
Block a user