vue3-zoomer
Version:
A Vue 3 component for zooming images.
601 lines (600 loc) • 18.9 kB
JavaScript
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
};