UNPKG

hana-img-viewer

Version:

A lightweight and easy-to-use image previewer for Vue 3

497 lines (496 loc) 17 kB
import { ref as d, computed as m, watch as _, onMounted as O, onUnmounted as U, watchEffect as P, onWatcherCleanup as ce, defineComponent as ve, shallowRef as fe, onBeforeUnmount as pe, createElementBlock as A, openBlock as N, Fragment as me, createBlock as G, createCommentVNode as D, createElementVNode as j, Teleport as K, normalizeStyle as J, unref as Q, mergeProps as he, toHandlers as we, toDisplayString as ge } from "vue"; function ee(e, n) { const t = n[0] - e[0], o = n[1] - e[1]; return Math.hypot(t, o); } function ye(e) { let n = 0; if (e) { let o = e; for (; o && o !== document.body; ) { const a = window.getComputedStyle(o).zIndex; if (a !== "auto" && a !== "") { const l = Number.parseInt(a, 10); Number.isNaN(l) || (n = Math.max(n, l)); } o = o.parentElement; } } const t = document.querySelectorAll("*"); for (const o of t) { const a = window.getComputedStyle(o).zIndex; if (a !== "auto" && a !== "") { const l = Number.parseInt(a, 10); Number.isNaN(l) || (n = Math.max(n, l)); } } return Math.max(n + 1, 9999); } function xe(e) { if (!e) return !1; let n = e; for (; n && n !== document.body; ) { const t = window.getComputedStyle(n), o = n.classList; if (t.position === "fixed" || o.contains("el-dialog") || o.contains("ant-modal") || o.contains("modal") || o.contains("dialog") || n.hasAttribute("role") && (n.getAttribute("role") === "dialog" || n.getAttribute("role") === "modal")) return !0; n = n.parentElement; } return !1; } function be(e) { let n = e.parentElement; for (; n && n !== document.body && n !== document.documentElement; ) { if (window.getComputedStyle(n).position === "fixed") return !0; n = n.parentElement; } return !1; } function Ee(e, n, t) { const o = e.getBoundingClientRect(); return be(e) ? { top: o.top, left: o.left } : { top: o.top + t, left: o.left + n }; } function te(e) { if (!e.value) return [0, 0]; const n = window.getComputedStyle(e.value).transform; if (n === "none") return [0, 0]; const t = n.match(/matrix\((.+)\)/); if (!t) return [0, 0]; const o = t[1].split(", "); return [ Number.parseFloat(o[4]) || 0, Number.parseFloat(o[5]) || 0 ]; } function B(e) { typeof e > "u" || typeof e.preventDefault != "function" || e.preventDefault(); } function Ie(e, n) { Object.assign(e.style, n); } function Le(e) { const { imgRef: n, props: t } = e, o = d(t.previewZIndex), a = d(!1), l = m(() => t.autoZIndex ? o.value : t.previewZIndex), h = () => { if (!(!t.autoZIndex || !n.value)) if (a.value = xe(n.value), a.value) { const s = ye(n.value); o.value = Math.max(s, t.previewZIndex); } else o.value = t.previewZIndex; }; return _(n, () => { h(); }, { immediate: !0 }), _(() => [t.autoZIndex, t.previewZIndex], () => { h(); }), { finalZIndex: l, isInModal: a, updateZIndex: h }; } function Se(e, n, t) { var o = t || {}, a = o.noTrailing, l = a === void 0 ? !1 : a, h = o.noLeading, s = h === void 0 ? !1 : h, x = o.debounceMode, v = x === void 0 ? void 0 : x, w, f = !1, p = 0; function b() { w && clearTimeout(w); } function T(S) { var E = S || {}, c = E.upcomingOnly, k = c === void 0 ? !1 : c; b(), f = !k; } function C() { for (var S = arguments.length, E = new Array(S), c = 0; c < S; c++) E[c] = arguments[c]; var k = this, g = Date.now() - p; if (f) return; function L() { p = Date.now(), n.apply(k, E); } function M() { w = void 0; } !s && v && !w && L(), b(), v === void 0 && g > e ? s ? (p = Date.now(), l || (w = setTimeout(v ? M : L, e))) : L() : l !== !0 && (w = setTimeout(v ? M : L, v === void 0 ? e - g : e)); } return C.cancel = T, C; } function Me(e, n = {}) { const t = d(null); let o = null; const a = () => { if (!e.value) return; const s = e.value.getBoundingClientRect(); t.value = s, n.onChange?.(s); }, l = n.throttle ? Se(n.throttleDelay ?? 100, a, { noTrailing: !1 }) : a, h = () => { o?.disconnect(), o = null, window.removeEventListener("resize", l); }; return O(() => { if (e.value && (a(), typeof ResizeObserver < "u" && (o = new ResizeObserver(l), o.observe(e.value)), window.addEventListener("resize", l), e.value.tagName === "IMG")) { const s = e.value; s.complete || s.addEventListener("load", a, { once: !0 }); } }), U(h), P(() => { if (h(), !!e.value) { if (a(), typeof ResizeObserver < "u" && (o = new ResizeObserver(l), o.observe(e.value)), window.addEventListener("resize", l), e.value.tagName === "IMG") { const s = e.value; s.complete || s.addEventListener("load", a, { once: !0 }); } ce(h); } }), { rect: t }; } function ke(e) { const { handleWheel: n, handleTouchStart: t, handleKeyDown: o } = e; function a(l) { if (!(typeof window > "u")) switch (l) { case "on": window.addEventListener("wheel", B, { passive: !1 }), window.addEventListener("touchmove", B, { passive: !1 }), window.addEventListener("wheel", n), window.addEventListener("touchstart", t), window.addEventListener("keydown", o); break; case "off": window.removeEventListener("wheel", B), window.removeEventListener("touchmove", B), window.removeEventListener("wheel", n), window.removeEventListener("touchstart", t), window.removeEventListener("keydown", o); break; } } return { toggleEventListener: a }; } function Te(e, n) { const { zoomStep: t, zoomMax: o, zoomMin: a, dblClickZoomTo: l } = n, h = d(!1), s = d(0), x = d(0), v = d(1); let w = 0, f = 0, p = 0, b = 0, T = 0; P(() => { e.value && (e.value.style.cursor = h.value ? "grabbing" : "grab"); }); const C = m(() => { const u = s.value === 0 ? "-50%" : `${s.value}px`, r = x.value === 0 ? "-50%" : `${x.value}px`; return `translate(${u}, ${r}) scale(${v.value})`; }), S = () => { e.value && Ie(e.value, { transform: C.value }); }, E = (u = 0, r) => { r !== void 0 ? v.value = r : v.value += u, v.value = Math.max(a, Math.min(o, v.value)), S(); }, c = (u) => { E(u.deltaY < 0 ? t : -t); }, k = () => { E(0, v.value > 1 ? 1 : l); }, g = (u) => { if (e.value) { if (u.touches.length === 2) { const [r, y] = [u.touches[0], u.touches[1]], Z = ee( [r.pageX, r.pageY], [y.pageX, y.pageY] ), W = Z / w; E(0, v.value * W), w = Z; } else if (u.touches.length === 1) { const r = u.touches[0], y = r.pageX - f, Z = r.pageY - p; s.value = b + y, x.value = T + Z, S(); } } }, L = () => { document.removeEventListener("touchmove", g), document.removeEventListener("touchend", L); }, M = (u) => { if (e.value) { if (u.touches.length === 2) { const [r, y] = [u.touches[0], u.touches[1]]; w = ee( [r.pageX, r.pageY], [y.pageX, y.pageY] ); } else if (u.touches.length === 1) { const r = u.touches[0]; f = r.pageX, p = r.pageY; const y = te(e); b = y[0], T = y[1]; } document.addEventListener("touchmove", g), document.addEventListener("touchend", L); } }, $ = (u) => { if (!e.value) return; const r = u.clientX - f, y = u.clientY - p; s.value = b + r, x.value = T + y, S(); }, Y = () => { h.value = !1, document.removeEventListener("mousemove", $), document.removeEventListener("mouseup", Y); }, H = (u) => { if (!e.value) return; h.value = !0, f = u.clientX, p = u.clientY; const r = te(e); b = r[0], T = r[1], document.addEventListener("mousemove", $), document.addEventListener("mouseup", Y); }, X = () => { s.value = 0, x.value = 0, v.value = 1; }, z = () => { document.removeEventListener("wheel", c), document.removeEventListener("touchstart", M), document.removeEventListener("touchmove", g), document.removeEventListener("touchend", L), document.removeEventListener("mousemove", $), document.removeEventListener("mouseup", Y); }; return U(z), { handleWheel: c, handleTouchStart: M, handleDblclick: k, handleMouseDown: H, initTransformer: X, cleanupListeners: z }; } function Ce() { const e = d(typeof window < "u" ? window.innerWidth : 0), n = d(typeof window < "u" ? window.innerHeight : 0), t = d(typeof window < "u" ? window.scrollX : 0), o = d(typeof window < "u" ? window.scrollY : 0), a = () => { typeof window > "u" || (e.value = window.innerWidth, n.value = window.innerHeight); }, l = () => { typeof window > "u" || (t.value = window.scrollX || window.pageXOffset, o.value = window.scrollY || window.pageYOffset); }; return O(() => { a(), l(), window.addEventListener("resize", a, { passive: !0 }), window.addEventListener("scroll", l, { passive: !0 }); }), U(() => { typeof window > "u" || (window.removeEventListener("resize", a), window.removeEventListener("scroll", l)); }), { width: e, height: n, scrollX: t, scrollY: o }; } const I = { duration: 500, maskBgColor: "black", maskOpacity: 0.1, previewZIndex: 9999, autoZIndex: !0, previewMaxWidth: "80vw", previewMaxHeight: "80vh", zoomStep: 0.2, zoomMin: 0.2, zoomMax: 10, dblClickZoom: !0, dblClickZoomTo: 2 }, ze = { /* ===== 支持v-model绑定 ===== */ displaying: { type: Boolean, default: void 0 }, applyingPreviewStyles: { type: Boolean, default: void 0 }, isAnimating: { type: Boolean, default: void 0 }, src: { type: String, required: !0 }, alt: String, width: [String, Number], height: [String, Number], duration: { type: Number, default: I.duration }, maskBgColor: { type: String, default: I.maskBgColor }, maskOpacity: { type: Number, default: I.maskOpacity }, previewZIndex: { type: Number, default: I.previewZIndex }, autoZIndex: { type: Boolean, default: I.autoZIndex }, previewMaxWidth: { type: [String, Number], default: I.previewMaxWidth }, previewMaxHeight: { type: [String, Number], default: I.previewMaxHeight }, zoomStep: { type: Number, default: I.zoomStep }, zoomMin: { type: Number, default: I.zoomMin }, zoomMax: { type: Number, default: I.zoomMax }, dblClickZoom: { type: Boolean, default: I.dblClickZoom }, dblClickZoomTo: { type: Number, default: I.dblClickZoomTo } }, Ze = { "update:displaying": (e) => !0, "update:applyingPreviewStyles": (e) => !0, "update:isAnimating": (e) => !0, displayChange: (e) => !0, previewStylesChange: (e) => !0, animatingChange: (e) => !0 }, Ne = ["src"], _e = { style: { display: "flex", "flex-direction": "column", "align-items": "center", gap: "0.5rem" } }, $e = ["src", "alt"], Ye = { key: 0, style: { "font-size": "0.8rem", color: "#666" } }, De = /* @__PURE__ */ ve({ name: "HanaImgViewer", __name: "HanaImgViewer", props: ze, emits: Ze, setup(e, { emit: n }) { const t = e, o = n, a = d(!1); O(() => { a.value = !0; }); const l = d(null), h = d(null), s = d(null), x = d(!1), v = d(!1), w = d(!1), f = m({ get: () => t.displaying !== void 0 ? t.displaying : x.value, set: (i) => { t.displaying !== void 0 ? o("update:displaying", i) : x.value = i, o("displayChange", i); } }), p = m({ get: () => t.applyingPreviewStyles !== void 0 ? t.applyingPreviewStyles : v.value, set: (i) => { t.applyingPreviewStyles !== void 0 ? o("update:applyingPreviewStyles", i) : v.value = i, o("previewStylesChange", i); } }), b = m({ get: () => t.isAnimating !== void 0 ? t.isAnimating : w.value, set: (i) => { t.isAnimating !== void 0 ? o("update:isAnimating", i) : w.value = i, o("animatingChange", i); } }); _(() => t.displaying, (i) => { i !== void 0 && i !== x.value && (x.value = i); }), _(() => t.applyingPreviewStyles, (i) => { i !== void 0 && i !== v.value && (v.value = i); }), _(() => t.isAnimating, (i) => { i !== void 0 && i !== w.value && (w.value = i); }); function T() { b.value = !0, setTimeout(() => { b.value = !1; }, t.duration); } const C = d(0), S = m(() => ({ width: (typeof t.width == "number" ? `${t.width}px` : t.width) ?? "fit-content", height: (typeof t.height == "number" ? `${t.height}px` : t.height) ?? "fit-content", visibility: f.value ? "hidden" : "visible" })); function E() { b.value || (f.value || C.value++, T(), f.value ? (p.value = !1, setTimeout(() => { f.value = !1; }, t.duration)) : f.value = !0); } const c = fe(null); O(() => { c.value = Te(s, t); }), pe(() => { c.value?.cleanupListeners(); }); const k = d({}), { rect: g } = Me(l, { throttle: !0, throttleDelay: 100 }), { finalZIndex: L } = Le({ imgRef: l, props: t }), M = m(() => g.value ? g.value.width / g.value.height : 0), { width: $, height: Y, scrollX: H, scrollY: X } = Ce(), z = m(() => $.value / Y.value), u = m(() => `${t.duration}ms`), r = m( () => g.value && M.value > z.value ? `${g.value.width}px` : "auto" ), y = m( () => g.value ? M.value > z.value ? "auto" : `${g.value.height}px` : "auto" ); function Z() { return l.value ? Ee(l.value, H.value, X.value) : { top: 0, left: 0 }; } const W = m(() => (C.value, `${Z().top}px`)), ne = m(() => (C.value, `${Z().left}px`)), oe = m( () => M.value > z.value ? `${t.previewMaxWidth}` : "auto" ), ie = m( () => M.value > z.value ? "auto" : `${t.previewMaxHeight}` ), ae = m(() => `calc(50vh + ${X.value}px)`); function le(i) { i.key === "Escape" && f.value && E(); } const R = d({ toggleEventListener: (i) => { } }); P(() => { c.value && (R.value = ke({ handleWheel: c.value.handleWheel, handleTouchStart: c.value.handleTouchStart, handleKeyDown: le })); }); const ue = m(() => ({ opacity: p.value ? t.maskOpacity : 0 })), re = m(() => ({ transition: b.value ? `all ${u.value}` : "none", width: p.value ? oe.value : r.value, height: p.value ? ie.value : y.value, top: p.value ? ae.value : W.value, left: p.value ? "50%" : ne.value, transform: p.value ? "translate(-50%, -50%)" : "none" })); return P(() => { c.value?.initTransformer(), f.value ? (typeof document < "u" && (document.body.style.overflow = "hidden"), requestAnimationFrame(() => { p.value = !0; })) : typeof document < "u" && (document.body.style.overflow = "auto"); }), _([f, b], ([i, F], [q, V]) => { if (!c.value) return; const se = i && !F && q && V && p.value, de = i && F && q && !V; se && (k.value = { dblclick: c.value.handleDblclick, mousedown: c.value.handleMouseDown }, R.value.toggleEventListener("on")), de && (R.value.toggleEventListener("off"), k.value = {}); }), (i, F) => (N(), A(me, null, [ a.value ? (N(), G(K, { key: 0, to: "body" }, [ f.value ? (N(), A("div", { key: 0, ref_key: "maskRef", ref: h, style: J({ position: "fixed", top: 0, left: 0, width: "100%", height: "100%", backgroundColor: i.maskBgColor, zIndex: Q(L) - 1, transition: `all ${u.value}`, opacity: ue.value.opacity }), onClick: E }, null, 4)) : D("", !0) ])) : D("", !0), a.value ? (N(), G(K, { key: 1, to: "body" }, [ f.value ? (N(), A("img", he({ key: 0, ref_key: "previewerRef", ref: s, src: i.src, draggable: "false", style: { position: "absolute", objectFit: "cover", cursor: "grab", zIndex: Q(L), ...re.value } }, we(k.value, !0)), null, 16, Ne)) : D("", !0) ])) : D("", !0), j("div", _e, [ j("div", { style: J(S.value) }, [ j("img", { ref_key: "imgRef", ref: l, src: i.src, alt: i.alt, style: { width: "100%", height: "100%", "object-fit": "cover", cursor: "pointer" }, onClick: E }, null, 8, $e) ], 4), i.alt ? (N(), A("span", Ye, ge(i.alt), 1)) : D("", !0) ]) ], 64)); } }), Xe = [De]; function Ae(e) { Xe.forEach((n) => { e.component(n.name || "HanaImgViewer", n); }); } const Oe = { install: Ae }; export { De as HanaImgViewer, Oe as default, I as defaultImgViewerProps, Ze as imgViewerEmitsObj, ze as imgViewerPropsObj };