From ba0934b2e158675d1831e5e299480c6382283fa0 Mon Sep 17 00:00:00 2001 From: Xin Date: Thu, 11 Sep 2025 09:54:35 +0100 Subject: [PATCH] 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. --- assets/css/components/image-zoom.css | 28 +++++++++------ assets/js/image-zoom.js | 54 +++++++++++++++------------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/assets/css/components/image-zoom.css b/assets/css/components/image-zoom.css index a1a543a..323338d 100644 --- a/assets/css/components/image-zoom.css +++ b/assets/css/components/image-zoom.css @@ -1,4 +1,3 @@ -/* Minimal styles for Hextra image zoom overlay */ .hextra-zoom-image-overlay { position: fixed; inset: 0; @@ -8,27 +7,40 @@ justify-content: center; z-index: 9999; opacity: 0; - transition: opacity 0.25s ease-out; + transition: opacity 260ms cubic-bezier(0.2, 0, 0, 1); cursor: zoom-out; - overscroll-behavior: contain; - touch-action: none; + overscroll-behavior: auto; + 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 { opacity: 1; } +.hextra-zoom-image-overlay.closing { + opacity: 0; + transition: opacity 360ms cubic-bezier(0.2, 0, 0, 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; + will-change: transform; transform: scale(0.98); } .hextra-zoom-image-overlay.show .hextra-zoom-image { 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) { @@ -42,9 +54,3 @@ .content img:not([data-no-zoom]) { cursor: zoom-in; } - -html:has(.hextra-zoom-image-overlay.show), -body:has(.hextra-zoom-image-overlay.show) { - overflow: hidden; - height: 100%; -} diff --git a/assets/js/image-zoom.js b/assets/js/image-zoom.js index 5e1eb81..783d362 100644 --- a/assets/js/image-zoom.js +++ b/assets/js/image-zoom.js @@ -1,16 +1,14 @@ -// 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 +/*! + * Hextra Image Zoom + * - Zooms images inside `.content` into a dark, blurred overlay. + * - 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 ready(fn) { - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", fn, { once: true }); - } else { - fn(); - } - } + 'use strict'; function createOverlay(src, alt) { const overlay = document.createElement("div"); @@ -25,33 +23,41 @@ overlay.appendChild(img); - function close() { - overlay.classList.remove("show"); - document.documentElement.style.removeProperty("overflow"); + function close(immediate = false) { + // trigger dedicated closing transitions for smoother zoom-out + overlay.classList.add("closing"); window.removeEventListener("keydown", onKeyDown, true); - overlay.addEventListener( - "transitionend", - () => overlay.remove(), - { once: true } - ); + window.removeEventListener("scroll", onScroll, true); + overlay.removeEventListener("wheel", onWheel); + + if (immediate) { + overlay.remove(); + return; + } + overlay.addEventListener("transitionend", () => overlay.remove(), { once: true }); } function onKeyDown(e) { if (e.key === "Escape") close(); } - overlay.addEventListener("click", close, { once: true }); + overlay.addEventListener("click", () => close(false), { once: 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); - // lock scroll - document.documentElement.style.overflow = "hidden"; // trigger fade-in 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"); if (!container) return; @@ -72,5 +78,5 @@ }, true ); - }); + }, { once: true }); })();