mirror of
https://github.com/imfing/hextra.git
synced 2025-09-15 12:01:59 -04:00

- Added support for pinch gestures to enhance the zoom experience on touch devices. - Implemented event listeners for pointer events to manage pinch start and end. - Updated closing behavior to account for active pinch gestures, improving user interaction.
124 lines
3.9 KiB
JavaScript
124 lines
3.9 KiB
JavaScript
/*!
|
|
* 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 () {
|
|
'use strict';
|
|
|
|
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);
|
|
|
|
// Track pinch gesture
|
|
let pinching = false;
|
|
let pinchTimer = 0;
|
|
const activeTouchPointers = new Set();
|
|
function pinchingStart() {
|
|
pinching = true;
|
|
if (pinchTimer) clearTimeout(pinchTimer);
|
|
}
|
|
function pinchingEndSoon() {
|
|
if (pinchTimer) clearTimeout(pinchTimer);
|
|
pinchTimer = setTimeout(() => (pinching = false), 350);
|
|
}
|
|
function onPointerDown(e) {
|
|
if (e.pointerType === 'touch') {
|
|
activeTouchPointers.add(e.pointerId);
|
|
if (activeTouchPointers.size > 1) pinchingStart();
|
|
}
|
|
}
|
|
function onPointerUp(e) {
|
|
if (e.pointerType === 'touch') {
|
|
activeTouchPointers.delete(e.pointerId);
|
|
if (activeTouchPointers.size < 2) pinchingEndSoon();
|
|
}
|
|
}
|
|
|
|
function close(immediate = false) {
|
|
// trigger dedicated closing transitions for smoother zoom-out
|
|
overlay.classList.add("closing");
|
|
window.removeEventListener("keydown", onKeyDown, true);
|
|
window.removeEventListener("scroll", onScroll, true);
|
|
overlay.removeEventListener("wheel", onWheel);
|
|
overlay.removeEventListener("pointerdown", onPointerDown);
|
|
overlay.removeEventListener("pointerup", onPointerUp);
|
|
overlay.removeEventListener("pointercancel", onPointerUp);
|
|
activeTouchPointers.clear();
|
|
if (pinchTimer) clearTimeout(pinchTimer);
|
|
|
|
if (immediate) {
|
|
overlay.remove();
|
|
return;
|
|
}
|
|
overlay.addEventListener("transitionend", () => overlay.remove(), { once: true });
|
|
}
|
|
|
|
function onKeyDown(e) {
|
|
if (e.key === "Escape") close();
|
|
}
|
|
|
|
overlay.addEventListener("click", () => close(false), { once: true });
|
|
window.addEventListener("keydown", onKeyDown, true);
|
|
|
|
function onWheel(e) {
|
|
// Ignore trackpad pinch (wheel + ctrlKey) and active pinch
|
|
if ((e && e.ctrlKey) || pinching) return;
|
|
close(true);
|
|
}
|
|
function onScroll() {
|
|
if (pinching) return;
|
|
close(true);
|
|
}
|
|
|
|
overlay.addEventListener("wheel", onWheel, { passive: true });
|
|
// Standard W3C pointer events for multi-touch pinch detection
|
|
overlay.addEventListener("pointerdown", onPointerDown);
|
|
overlay.addEventListener("pointerup", onPointerUp);
|
|
overlay.addEventListener("pointercancel", onPointerUp);
|
|
window.addEventListener("scroll", onScroll, true);
|
|
|
|
document.body.appendChild(overlay);
|
|
|
|
// trigger fade-in
|
|
requestAnimationFrame(() => overlay.classList.add("show"));
|
|
}
|
|
|
|
// Initialize after DOM is parsed; defer script ensures this usually fires immediately
|
|
document.addEventListener('DOMContentLoaded', 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
|
|
);
|
|
}, { once: true });
|
|
})();
|