chore(image-zoom): enhance zoom functionality with improved transitions and closing behavior

- Updated CSS for smoother transitions and added closing effects for the zoom overlay.
- Enhanced JavaScript to support dedicated closing transitions and improved event handling for dismissing the overlay.
- Removed unnecessary scroll lock and overflow styles for better user experience.
This commit is contained in:
Xin
2025-09-11 09:54:35 +01:00
parent a528d9adc0
commit ba0934b2e1
2 changed files with 47 additions and 35 deletions

View File

@@ -1,4 +1,3 @@
/* Minimal styles for Hextra image zoom overlay */
.hextra-zoom-image-overlay { .hextra-zoom-image-overlay {
position: fixed; position: fixed;
inset: 0; inset: 0;
@@ -8,27 +7,40 @@
justify-content: center; justify-content: center;
z-index: 9999; z-index: 9999;
opacity: 0; opacity: 0;
transition: opacity 0.25s ease-out; transition: opacity 260ms cubic-bezier(0.2, 0, 0, 1);
cursor: zoom-out; cursor: zoom-out;
overscroll-behavior: contain; overscroll-behavior: auto;
touch-action: none; touch-action: auto;
backdrop-filter: blur(var(--hextra-image-zoom-blur, 4px));
-webkit-backdrop-filter: blur(var(--hextra-image-zoom-blur, 4px));
} }
.hextra-zoom-image-overlay.show { .hextra-zoom-image-overlay.show {
opacity: 1; opacity: 1;
} }
.hextra-zoom-image-overlay.closing {
opacity: 0;
transition: opacity 360ms cubic-bezier(0.2, 0, 0, 1);
}
.hextra-zoom-image { .hextra-zoom-image {
max-width: min(95vw, 1200px); max-width: min(95vw, 1200px);
max-height: 95vh; max-height: 95vh;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.35); box-shadow: 0 8px 40px rgba(0, 0, 0, 0.35);
transition: transform 0.3s ease-out; will-change: transform;
transform: scale(0.98); transform: scale(0.98);
} }
.hextra-zoom-image-overlay.show .hextra-zoom-image { .hextra-zoom-image-overlay.show .hextra-zoom-image {
transform: scale(1); transform: scale(1);
transition: transform 320ms cubic-bezier(0.2, 0.8, 0.2, 1);
}
.hextra-zoom-image-overlay.closing .hextra-zoom-image {
transform: scale(0.98);
transition: transform 340ms cubic-bezier(0.3, 0, 0.2, 1);
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
@@ -42,9 +54,3 @@
.content img:not([data-no-zoom]) { .content img:not([data-no-zoom]) {
cursor: zoom-in; cursor: zoom-in;
} }
html:has(.hextra-zoom-image-overlay.show),
body:has(.hextra-zoom-image-overlay.show) {
overflow: hidden;
height: 100%;
}

View File

@@ -1,16 +1,14 @@
// Minimal, dependency-free image zoom for Hextra /*!
// - Activates on images inside `.content` * Hextra Image Zoom
// - Close on overlay click or Escape * - Zooms images inside `.content` into a dark, blurred overlay.
// - Opt-out with `data-no-zoom` on <img> * - Dismiss: overlay click, Esc, wheel/scroll (non-ctrl).
* - Pinch/trackpad pinch (wheel+ctrl) will NOT dismiss.
* - Opt out per image via `data-no-zoom`.
* - Customize via CSS vars: --hextra-image-zoom-backdrop, --hextra-image-zoom-blur.
*/
(function () { (function () {
function ready(fn) { 'use strict';
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", fn, { once: true });
} else {
fn();
}
}
function createOverlay(src, alt) { function createOverlay(src, alt) {
const overlay = document.createElement("div"); const overlay = document.createElement("div");
@@ -25,33 +23,41 @@
overlay.appendChild(img); overlay.appendChild(img);
function close() { function close(immediate = false) {
overlay.classList.remove("show"); // trigger dedicated closing transitions for smoother zoom-out
document.documentElement.style.removeProperty("overflow"); overlay.classList.add("closing");
window.removeEventListener("keydown", onKeyDown, true); window.removeEventListener("keydown", onKeyDown, true);
overlay.addEventListener( window.removeEventListener("scroll", onScroll, true);
"transitionend", overlay.removeEventListener("wheel", onWheel);
() => overlay.remove(),
{ once: true } if (immediate) {
); overlay.remove();
return;
}
overlay.addEventListener("transitionend", () => overlay.remove(), { once: true });
} }
function onKeyDown(e) { function onKeyDown(e) {
if (e.key === "Escape") close(); if (e.key === "Escape") close();
} }
overlay.addEventListener("click", close, { once: true }); overlay.addEventListener("click", () => close(false), { once: true });
window.addEventListener("keydown", onKeyDown, true); window.addEventListener("keydown", onKeyDown, true);
function onWheel(e) { if (e && e.ctrlKey) return; close(true); }
function onScroll() { close(true); }
overlay.addEventListener("wheel", onWheel, { passive: true });
window.addEventListener("scroll", onScroll, true);
document.body.appendChild(overlay); document.body.appendChild(overlay);
// lock scroll
document.documentElement.style.overflow = "hidden";
// trigger fade-in // trigger fade-in
requestAnimationFrame(() => overlay.classList.add("show")); requestAnimationFrame(() => overlay.classList.add("show"));
} }
ready(function () { // Initialize after DOM is parsed; defer script ensures this usually fires immediately
document.addEventListener('DOMContentLoaded', function () {
const container = document.querySelector(".content"); const container = document.querySelector(".content");
if (!container) return; if (!container) return;
@@ -72,5 +78,5 @@
}, },
true true
); );
}); }, { once: true });
})(); })();