UNPKG

vue3-zoomer

Version:

A Vue 3 component for zooming images.

601 lines (600 loc) 18.9 kB
import "./style.css"; import { ref as b, defineComponent as P, mergeModels as R, useModel as k, computed as E, useTemplateRef as O, createElementBlock as x, openBlock as w, withModifiers as C, normalizeClass as j, createElementVNode as I, normalizeStyle as T, unref as D, onMounted as q, onBeforeUnmount as ee, watchEffect as oe, onUpdated as te, createBlock as Y, renderSlot as L, createCommentVNode as W, withDirectives as A, resolveDynamicComponent as ne, mergeProps as H, vShow as V } from "vue"; function F(e, s, o) { if (s) { const t = s.clientWidth, n = s.clientHeight, l = e.left / t, m = e.top / n, v = t * o, d = n * o; let r = -(v - t) * l, f = -(d - n) * m; return r = Math.max(Math.min(r, 0), t - v), f = Math.max(Math.min(f, 0), n - d), { left: r, top: f }; } else return console.error("element not found"), { left: 0, top: 0 }; } function le(e, s, o, t, n) { if (t) { const l = t.clientWidth, m = t.clientHeight, v = m - m * n, d = l - l * n, r = s.left - e.left, f = s.top - e.top; return { left: Math.max(Math.min(o.left + r, 0), d), top: Math.max(Math.min(o.top + f, 0), v) }; } else return console.error("element not found"), { left: 0, top: 0 }; } const G = (e, s) => ({ left: -e.left * s, top: -e.top * s }), se = (e, s) => ({ left: -e.left / s, top: -e.top / s }); function J(e) { const { clientX: s, clientY: o } = e.changedTouches[0]; return { left: s, top: o }; } function ae(e, s) { const { clientX: o, clientY: t } = e.changedTouches[0], n = { left: 0, top: 0 }; if (s) { const l = s.getBoundingClientRect(); n.left = o - l.left, n.top = t - l.top; } else return console.error("element not found"), n; return n; } function y(e, s) { if (s) { const o = s.getBoundingClientRect(), t = e.clientX - o.left, n = e.clientY - o.top, l = e.clientX < o.left || e.clientX > o.right || e.clientY < o.top || e.clientY > o.bottom; return { pos: { left: t, top: n }, isOutside: l }; } return { pos: { left: 0, top: 0 }, isOutside: !0 }; } function K() { const e = b(!1); return { isTransition: e, startTransition: (o = 150) => { e.value = !0, setTimeout(() => { e.value = !1; }, o); } }; } function Q(e, s, o, t = 2) { const n = b("IN"); return { zoomDir: n, multiStepZoomIn: (m, v, d) => { let r = m; n.value === "IN" ? r = Math.min(m + d, t) : r = Math.max(m - d, 1), r === 1 ? n.value = "IN" : r === t && (n.value = "OUT"), e.value = r, s.value = F(v, o.value, r); } }; } const re = ["src", "alt"], ie = /* @__PURE__ */ P({ __name: "DragZoomImg", props: /* @__PURE__ */ R({ src: {}, alt: {}, zoomScale: { default: 2 }, trigger: { default: "click" }, step: {}, persist: { type: Boolean } }, { currentScale: { default: 1 }, currentScaleModifiers: {}, zoomedImgOffset: { default: { left: 0, top: 0 } }, zoomedImgOffsetModifiers: {} }), emits: /* @__PURE__ */ R(["error", "load"], ["update:currentScale", "update:zoomedImgOffset"]), setup(e, { expose: s }) { const o = e, t = k(e, "currentScale"), n = k(e, "zoomedImgOffset"), l = b({ left: 0, top: 0 }), m = b({ left: 0, top: 0 }), v = b(!1), d = E(() => t.value > 1), r = O("container"), f = O("vzImg"), { isTransition: u, startTransition: p } = K(), { multiStepZoomIn: g, zoomDir: h } = Q( t, n, r, o.zoomScale ), $ = (c) => { if (o.trigger === "hover") { const { pos: z } = y(c, r.value); p(), g(t.value, z, o.step ?? o.zoomScale); } }, S = () => { v.value = !1, o.trigger === "hover" && N(); }, a = (c) => { const z = B(c); l.value = z, m.value = z, d.value && (v.value = !0); }, i = (c) => { if (!v.value) return; const z = B(c); n.value = le( l.value, z, n.value, r.value, t.value ), l.value = z; }, M = (c) => { v.value = !1; const z = B(c); if (m.value.left === z.left && m.value.top === z.top) { const Z = _( d.value ? new MouseEvent("mouseup", U()) : c ); p(), g(t.value, Z, o.step ?? o.zoomScale); } }, N = () => { u.value = !0, t.value = 1, n.value = { left: 0, top: 0 }, h.value = "IN"; }, B = (c) => c instanceof MouseEvent ? { left: c.clientX, top: c.clientY } : J(c), _ = (c) => { if (c instanceof MouseEvent) { const { pos: z } = y(c, r.value); return z; } else return ae(c, r.value); }, U = () => { var Z; const c = (Z = r.value) == null ? void 0 : Z.getBoundingClientRect(), z = t.value - 1 === 0 ? 1 : t.value - 1; return { clientX: -n.value.left / z + ((c == null ? void 0 : c.left) ?? 0), clientY: -n.value.top / z + ((c == null ? void 0 : c.top) ?? 0) }; }; return s({ zoomDir: h, vzImgRef: f, multiZoom: () => { const c = U(); a(new MouseEvent("mousedown", c)), M(new MouseEvent("mouseup", c)); } }), (c, z) => (w(), x("div", { class: j(["vz-zoomimg-img-container overflow-hidden", { "cursor-grab": d.value && !v.value, "cursor-grabbing": d.value && v.value, "cursor-zoom-in": !d.value }]), ref: "container", onMouseenter: $, onMousedown: a, onMousemove: i, onMouseup: M, onMouseleave: S, onTouchstart: C(a, ["prevent"]), onTouchmove: C(i, ["prevent"]), onTouchend: C(M, ["prevent"]) }, [ I("img", { class: "vz-zoomimg-img zoom-effect h-full w-full object-fill", draggable: "false", ref: "vzImg", src: c.src, alt: c.alt, style: T({ transform: `translate(${n.value.left}px, ${n.value.top}px) scale(${t.value})`, transformOrigin: "0 0", transition: D(u) ? "transform 100ms ease-in-out" : "none" }), onError: z[0] || (z[0] = (Z) => c.$emit("error")), onLoad: z[1] || (z[1] = (Z) => c.$emit("load")) }, null, 44, re) ], 34)); } }), ue = ["src", "alt"], ce = /* @__PURE__ */ P({ __name: "MoveZoomImg", props: /* @__PURE__ */ R({ src: {}, alt: {}, zoomScale: { default: 2 }, trigger: { default: "click" }, step: {}, persist: { type: Boolean } }, { currentScale: { default: 1 }, currentScaleModifiers: {}, zoomedImgOffset: { default: { left: 0, top: 0 } }, zoomedImgOffsetModifiers: {} }), emits: /* @__PURE__ */ R(["error", "load"], ["update:currentScale", "update:zoomedImgOffset"]), setup(e, { expose: s }) { const o = e, t = k(e, "currentScale"), n = k(e, "zoomedImgOffset"), l = E(() => t.value > 1), m = O("container"), v = O("vzImg"), { isTransition: d, startTransition: r } = K(), { zoomDir: f, multiStepZoomIn: u } = Q( t, n, m, o.zoomScale ), p = (a) => { if (o.trigger === "hover") { const { pos: i } = y(a, m.value); r(), u(t.value, i, o.step ?? o.zoomScale); } }, g = () => { o.persist || S(); }, h = (a) => { const { pos: i } = y(a, m.value); r(), u(t.value, i, o.step ?? o.zoomScale); }, $ = (a) => { const { pos: i, isOutside: M } = y( a, m.value ); !M && l.value && !d.value && (n.value = F( i, m.value, t.value )); }, S = () => { d.value = !0, t.value = 1, n.value = { left: 0, top: 0 }, f.value = "IN"; }; return s({ zoomDir: f, vzImgRef: v, multiZoom: () => { var i; const a = (i = m.value) == null ? void 0 : i.getBoundingClientRect(); a && h({ clientX: a.x + a.width / 2, clientY: a.y + a.height / 2 }); } }), (a, i) => (w(), x("div", { class: "vz-zoomimg-img-container cursor-zoom-in overflow-clip border-none", ref: "container", style: T({ cursor: D(f) === "OUT" ? "zoom-out" : "zoom-in" }), onMouseenter: p, onMouseleave: g, onMousemove: $, onClick: h }, [ I("img", { class: "vz-zoomimg-img h-full w-full object-fill", ref: "vzImg", src: a.src, alt: a.alt, style: T({ transform: `translate(${n.value.left}px, ${n.value.top}px) scale(${t.value})`, transformOrigin: "0 0", transition: D(d) ? "transform 100ms ease-in-out" : "none" }), onError: i[0] || (i[0] = (M) => a.$emit("error")), onLoad: i[1] || (i[1] = (M) => a.$emit("load")) }, null, 44, ue) ], 36)); } }), me = { class: "vz-zoom-btns-container absolute bottom-1 end-1 z-10 flex gap-2 p-3" }, fe = ["disabled"], ve = ["disabled"], de = /* @__PURE__ */ P({ __name: "ZoomButtons", props: { maxZoom: { type: Boolean, required: !0 }, minZoom: { type: Boolean, required: !0 } }, emits: ["zoomIn", "zoomOut"], setup(e) { return (s, o) => (w(), x("div", me, [ I("button", { class: "controls-btn zoom-out-btn", disabled: e.minZoom, onClick: o[0] || (o[0] = (t) => s.$emit("zoomOut")) }, null, 8, fe), I("button", { class: "controls-btn zoom-in-btn", disabled: e.maxZoom, onClick: o[1] || (o[1] = (t) => s.$emit("zoomIn")) }, null, 8, ve) ])); } }), X = (e, s) => { const o = e.__vccOpts || e; for (const [t, n] of s) o[t] = n; return o; }, pe = /* @__PURE__ */ X(de, [["__scopeId", "data-v-2c73679c"]]), ge = ["alt", "src"], ze = /* @__PURE__ */ P({ __name: "ZoomMap", props: /* @__PURE__ */ R({ src: { type: String, required: !0 }, alt: { type: String, default: "zoomed-img" }, zoomScale: { type: Number, default: 2 } }, { position: { type: Object }, positionModifiers: {} }), emits: ["update:position"], setup(e) { const s = k(e, "position"), o = b(!1), t = O("backdrop"), n = O("movableWindow"), l = b(0), m = b(0), v = (p) => { if (t.value) { const { pos: g } = y(p, t.value); l.value = g.left, m.value = g.top; } }; q(() => { t.value && t.value.addEventListener("mousemove", v); }), ee(() => { t.value && t.value.removeEventListener("mousemove", v); }), oe(() => { o.value && d(); }); const d = () => { s.value = u(); }, r = () => { o.value = !0; }, f = () => { o.value = !1; }, u = () => { var p, g, h, $, S, a; if (t.value && n.value) { const i = ((p = t.value) == null ? void 0 : p.clientHeight) - ((g = n.value) == null ? void 0 : g.clientHeight), M = ((h = t.value) == null ? void 0 : h.clientWidth) - (($ = n.value) == null ? void 0 : $.clientWidth), N = Math.min( Math.max( m.value - Number((S = n.value) == null ? void 0 : S.clientHeight) / 2, 0 ), i ), B = Math.min( Math.max( l.value - Number((a = n.value) == null ? void 0 : a.clientWidth) / 2, 0 ), M ); return { top: N, left: B }; } else console.error("couldn't get movable window new position"); }; return (p, g) => { var h, $; return w(), x("div", { class: "vz-map-container relative select-none overflow-clip", ref: "backdrop", onClick: d, onMouseleave: f }, [ I("img", { class: "vz-map-img h-full w-full object-fill", ref: "imgRef", alt: e.alt, src: e.src }, null, 8, ge), I("div", { class: j(["vz-map-window absolute bg-white/20 hover:cursor-pointer active:cursor-move", { "invisible -z-10": !s.value }]), ref: "movableWindow", style: T({ left: `${(h = s.value) == null ? void 0 : h.left}px`, top: `${($ = s.value) == null ? void 0 : $.top}px`, width: `${1 / e.zoomScale * 100}%`, height: `${1 / e.zoomScale * 100}%` }), onMousedown: r, onMouseup: f }, null, 38) ], 544); }; } }), he = { class: "vz-zoomimg-container relative" }, Me = /* @__PURE__ */ P({ __name: "ZoomImg", props: { zoomType: { default: "move" }, showZoomBtns: { type: Boolean }, showImgMap: { type: Boolean }, loading: { type: Boolean }, src: {}, alt: {}, zoomScale: {}, trigger: {}, step: {}, persist: { type: Boolean } }, emits: ["error", "load"], setup(e, { emit: s }) { const o = s, t = e, n = b(1), l = b({ left: 0, top: 0 }), m = b(1080), v = b(!1), d = b(!1), r = O("zoomComponent"), f = E( () => t.zoomType === "drag" || m.value < 768 ), u = E(() => { if (n.value !== 1) return se(l.value, n.value * 4); }), p = () => { r.value && (r.value.zoomDir = "IN", r.value.multiZoom()); }, g = () => { r.value && (r.value.zoomDir = "OUT", r.value.multiZoom()); }, h = (a) => { a && (l.value = G(a, n.value * 4)); }, $ = () => { v.value = !0, o("load"); }, S = () => { d.value = !0, o("error"); }; return q(() => { var a, i; m.value = (window == null ? void 0 : window.innerWidth) ?? 1080, (i = (a = r.value) == null ? void 0 : a.vzImgRef) != null && i.complete && (v.value = !0); }), te(() => { var a, i; v.value = !!((i = (a = r.value) == null ? void 0 : a.vzImgRef) != null && i.complete); }), (a, i) => (w(), x("div", he, [ (w(), Y(ne(f.value ? ie : ce), H({ "current-scale": n.value, "onUpdate:currentScale": i[0] || (i[0] = (M) => n.value = M), "zoomed-img-offset": l.value, "onUpdate:zoomedImgOffset": i[1] || (i[1] = (M) => l.value = M) }, t, { ref: "zoomComponent", class: ["h-full w-full", { hidden: !v.value && a.$slots.loading || d.value && a.$slots.error }], alt: a.alt, onError: S, onLoad: $ }), null, 16, ["current-scale", "zoomed-img-offset", "alt", "class"])), v.value ? W("", !0) : L(a.$slots, "loading", { key: 0 }, void 0, !0), d.value ? L(a.$slots, "error", { key: 1 }, void 0, !0) : W("", !0), a.showZoomBtns ? (w(), Y(pe, H({ key: 2, "zoomed-img-offset": l.value, "onUpdate:zoomedImgOffset": i[2] || (i[2] = (M) => l.value = M), "current-scale": n.value, "onUpdate:currentScale": i[3] || (i[3] = (M) => n.value = M) }, t, { "max-zoom": n.value === a.zoomScale, "min-zoom": n.value === 1, onZoomIn: p, onZoomOut: g }), null, 16, ["zoomed-img-offset", "current-scale", "max-zoom", "min-zoom"])) : W("", !0), a.showImgMap ? A((w(), Y(ze, { key: 3, class: "absolute bottom-[28%] left-0 box-content h-[25%] w-[25%] border-8 border-transparent outline outline-2 outline-offset-[-8px] outline-white", src: a.src, "zoom-scale": n.value, position: u.value, "onUpdate:position": h }, null, 8, ["src", "zoom-scale", "position"])), [ [V, u.value] ]) : W("", !0) ])); } }), be = /* @__PURE__ */ X(Me, [["__scopeId", "data-v-95680e56"]]), $e = ["alt", "src"], we = ["src"], Ie = /* @__PURE__ */ P({ __name: "Magnifier", props: { src: { type: String, required: !0 }, alt: { type: String, default: "zoomed-img" }, zoomScale: { type: Number, default: 2 }, size: { type: Number, default: 200 } }, emits: ["error", "load"], setup(e) { const s = e, o = O("container"), t = b({ left: 0, top: 0 }), n = b(!0), l = b(s.size), m = E(() => { const f = { left: t.value.left + l.value / 2, top: t.value.top + l.value / 2 }, u = G(f, s.zoomScale); return { left: u.left + l.value / 2, top: u.top + l.value / 2 }; }), v = (f) => { var p; if (o.value) { const { pos: g, isOutside: h } = y( f, o.value ); n.value = h, n.value || (t.value = { left: g.left - l.value / 2, top: g.top - l.value / 2 }); } const u = (p = o.value) == null ? void 0 : p.getBoundingClientRect(); t.value = { left: f.clientX - (u == null ? void 0 : u.left) - l.value / 2, top: f.clientY - (u == null ? void 0 : u.top) - l.value / 2 }; }, d = (f) => { var u, p; f.deltaY > 0 ? l.value = Math.max( (((u = o.value) == null ? void 0 : u.clientWidth) ?? 1) / 12, l.value - 10 ) : l.value = Math.min( (((p = o.value) == null ? void 0 : p.clientWidth) ?? 1) / 1.5, l.value + 10 ); }, r = (f) => { var g; const u = (g = o.value) == null ? void 0 : g.getBoundingClientRect(), p = J(f); t.value = { left: p.left - u.left - l.value / 2, top: p.top - u.top - l.value / 2 }; }; return (f, u) => { var p, g; return w(), x("div", { class: "vz-magnifier-img-container relative", ref: "container", onMousemove: v, onWheel: C(d, ["prevent"]), onTouchmove: C(r, ["prevent"]) }, [ I("img", { class: "vz-magnifier-img h-full w-full object-fill", alt: e.alt, src: e.src, onError: u[0] || (u[0] = (h) => f.$emit("error")), onLoad: u[1] || (u[1] = (h) => f.$emit("load")) }, null, 40, $e), A(I("div", { class: "vz-magnifier-lens absolute z-10 overflow-clip rounded-full shadow-inner-lg hover:cursor-none", style: T({ left: `${t.value.left}px`, top: `${t.value.top}px`, width: `${l.value}px`, height: `${l.value}px` }) }, [ I("img", { class: "vz-magnifier-lens-img h-full w-full object-fill", alt: "lens-zoomed-image", src: e.src, style: T({ transform: `translate(${m.value.left}px, ${m.value.top}px) scaleX(${e.zoomScale * (((p = o.value) == null ? void 0 : p.clientWidth) ?? 1) / l.value}) scaleY(${e.zoomScale * (((g = o.value) == null ? void 0 : g.clientHeight) ?? 1) / l.value})`, transformOrigin: "0 0" }) }, null, 12, we) ], 4), [ [V, !n.value] ]) ], 544); }; } }), Se = /* @__PURE__ */ X(Ie, [["__scopeId", "data-v-3c1bef25"]]), ye = { install: (e) => { e.component("ZoomImg", be), e.component("Magnifier", Se); } }; export { Se as Magnifier, be as ZoomImg, ye as default };