@zoom-image/core
Version:
A core implementation of zoom image
913 lines (900 loc) • 34.8 kB
JavaScript
var ZoomImage = (function (exports) {
'use strict';
// ../../node_modules/.pnpm/@namnode+store@0.1.0/node_modules/@namnode/store/dist/chunk-TZNK2OF3.mjs
function f(o) {
let r = /* @__PURE__ */ new Set(), s = false, a = o, e, c = (t = {}) => {
e = { ...e, ...t }, i();
}, i = () => {
if (s)
return;
let t = false;
if (e) {
for (let n in e)
if (a[n] !== e[n]) {
t = true;
break;
}
}
t && (a = { ...a, ...e }, r.forEach((n) => n({ state: a, updatedProperties: e })), e = void 0);
};
return { subscribe: (t) => (r.add(t), () => {
r.delete(t);
}), cleanup: () => r.clear(), getState: () => a, setState: c, batch: (t) => {
s = true, t(), s = false, i();
} };
}
// src/imageLoader.ts
var THRESHOLD = 50;
var makeImageLoader = () => {
const createZoomImage = (img, src, store) => {
if (img.src === src)
return;
img.src = src;
let complete = false;
img.onload = () => {
complete = true;
store.setState({ zoomedImgStatus: "loaded" });
};
img.onerror = () => {
complete = true;
store.setState({ zoomedImgStatus: "error" });
};
setTimeout(() => {
if (!complete)
store.setState({ zoomedImgStatus: "loading" });
}, THRESHOLD);
};
return {
createZoomImage
};
};
var imageLoader = makeImageLoader();
// src/utils.ts
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
function noop() {
}
function preventDefault(event) {
event.preventDefault();
}
var keySet = /* @__PURE__ */ new Set(["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"]);
function preventDefaultForScrollKeys(event) {
if (keySet.has(event.key)) {
preventDefault(event);
return false;
}
}
var controller = new AbortController();
var signal = controller.signal;
function disableScroll() {
window.addEventListener("DOMMouseScroll", preventDefault, { signal });
window.addEventListener("wheel", preventDefault, { passive: false, signal });
window.addEventListener("touchmove", preventDefault, { passive: false, signal });
window.addEventListener("keydown", preventDefaultForScrollKeys, { signal });
}
function enableScroll() {
controller?.abort();
}
function getSourceImage(container) {
if (!container) {
throw new Error("Please specify a container for the zoom image");
}
const sourceImgElement = container.querySelector("img");
if (!sourceImgElement) {
throw new Error("Please place an image inside the container");
}
return sourceImgElement;
}
function getPointersCenter(first, second) {
return {
x: (first.x + second.x) / 2,
y: (first.y + second.y) / 2
};
}
function computeZoomGesture(prev, curr) {
const prevCenter = getPointersCenter(prev[0], prev[1]);
const currCenter = getPointersCenter(curr[0], curr[1]);
const centerDist = { x: currCenter.x - prevCenter.x, y: currCenter.y - prevCenter.y };
const prevDistance = Math.hypot(prev[0].x - prev[1].x, prev[0].y - prev[1].y);
const currDistance = Math.hypot(curr[0].x - curr[1].x, curr[0].y - curr[1].y);
let scale = currDistance / prevDistance;
const eps = 1e-5;
if (Math.abs(scale - 1) < eps) {
scale = 1 + eps;
}
return {
scale,
center: {
// We shift the zoom center away such that the translation part of the gesture
// is also captured by the zoom operation.
x: prevCenter.x + centerDist.x / (1 - scale),
y: prevCenter.y + centerDist.y / (1 - scale)
}
};
}
function makeMaybeCallFunction(predicateFn, fn) {
return (arg) => {
if (predicateFn()) {
fn(arg);
}
};
}
var scaleLinear = ({
domainStart,
domainStop,
rangeStart,
rangeStop
}) => (value) => rangeStart + (rangeStop - rangeStart) * ((value - domainStart) / (domainStop - domainStart));
// src/createZoomImageHover.ts
function createZoomImageHover(container, options) {
const controller2 = new AbortController();
const { signal: signal2 } = controller2;
const sourceImgElement = getSourceImage(container);
const zoomedImgWrapper = document.createElement("div");
zoomedImgWrapper.style.overflow = "hidden";
const zoomedImg = zoomedImgWrapper.appendChild(document.createElement("img"));
zoomedImg.alt = options.zoomImageProps?.alt || "";
zoomedImg.style.maxWidth = "none";
zoomedImg.style.display = "none";
const zoomLens = container.appendChild(document.createElement("div"));
zoomLens.style.display = "none";
let sourceImageElementWidth = 0;
let sourceImageElementHeight = 0;
const finalOptions = {
zoomImageSource: options.zoomImageSource || sourceImgElement.src,
zoomLensClass: options.zoomLensClass || "",
zoomTargetClass: options.zoomTargetClass || "",
customZoom: options.customZoom,
scale: options.scale || 2,
zoomTarget: options.zoomTarget,
zoomLensScale: options.zoomLensScale || 1,
disableScrollLock: options.disableScrollLock || false
};
const {
scale,
zoomImageSource,
customZoom,
zoomLensClass,
zoomTarget,
zoomLensScale,
zoomTargetClass,
disableScrollLock
} = finalOptions;
const store = f({
zoomedImgStatus: "idle",
enabled: true
});
let offset = getOffset(sourceImgElement);
function getOffset(element) {
const elRect = element.getBoundingClientRect();
return { left: elRect.left, top: elRect.top };
}
function getLimitX(value) {
return sourceImageElementWidth - value;
}
function getLimitY(value) {
return sourceImageElementHeight - value;
}
function zoomLensLeft(left) {
const minX = zoomLens.clientWidth / 2;
return clamp(left, minX, getLimitX(minX)) - minX;
}
function zoomLensTop(top) {
const minY = zoomLens.clientHeight / 2;
return clamp(top, minY, getLimitY(minY)) - minY;
}
function processZoom(event) {
let offsetX;
let offsetY;
let backgroundX;
let backgroundY;
if (offset) {
offsetX = zoomLensLeft(event.clientX - offset.left);
offsetY = zoomLensTop(event.clientY - offset.top);
backgroundX = offsetX * scale / zoomLensScale;
backgroundY = offsetY * scale / zoomLensScale;
zoomedImg.style.transform = "translate(" + -backgroundX + "px," + -backgroundY + "px)";
zoomLens.style.cssText += "transform:translate(" + offsetX + "px," + offsetY + "px);";
}
}
async function handlePointerEnter() {
imageLoader.createZoomImage(zoomedImg, zoomImageSource, store);
zoomedImg.style.display = "block";
zoomLens.style.display = "block";
if (zoomTargetClass) {
const classes = zoomTargetClass.split(" ");
classes.forEach((className) => zoomTarget.classList.add(className));
}
if (!disableScrollLock)
disableScroll();
}
function handlePointerLeave() {
zoomedImg.style.display = "none";
zoomLens.style.display = "none";
if (zoomTargetClass) {
const classes = zoomTargetClass.split(" ");
classes.forEach((className) => zoomTarget.classList.remove(className));
}
if (!disableScrollLock)
enableScroll();
}
function handleScroll() {
offset = getOffset(sourceImgElement);
}
async function setup() {
if (zoomLensClass) {
zoomLens.className = zoomLensClass;
} else {
zoomLens.style.background = "rgba(238, 130, 238, 0.5)";
}
container.addEventListener("pointerdown", processZoom, { signal: signal2 });
container.addEventListener("pointermove", processZoom, { signal: signal2 });
container.addEventListener("pointerenter", handlePointerEnter, { signal: signal2 });
container.addEventListener("pointerleave", handlePointerLeave, { signal: signal2 });
window.addEventListener("scroll", handleScroll, { signal: signal2 });
container.addEventListener("touchend", enableScroll, { signal: signal2 });
zoomTarget.appendChild(zoomedImgWrapper);
await new Promise((resolve) => setTimeout(resolve, 1));
const containerRect = container.getBoundingClientRect();
sourceImageElementWidth = containerRect.width;
sourceImageElementHeight = containerRect.height;
if (customZoom) {
zoomedImgWrapper.style.width = customZoom.width + "px";
zoomedImgWrapper.style.height = customZoom.height + "px";
} else {
zoomedImgWrapper.style.width = sourceImageElementWidth + "px";
zoomedImgWrapper.style.height = sourceImageElementHeight + "px";
}
zoomedImg.width = sourceImageElementWidth * scale / zoomLensScale;
zoomedImg.height = sourceImageElementHeight * scale / zoomLensScale;
const sourceImageRect = sourceImgElement.getBoundingClientRect();
const fromLeft = sourceImageRect.left - containerRect.left;
const fromTop = sourceImageRect.top - containerRect.top;
zoomTarget.style.pointerEvents = "none";
zoomLens.style.position = "absolute";
zoomLens.style.left = fromLeft + "px";
zoomLens.style.top = fromTop + "px";
zoomLens.style.width = customZoom.width / scale * zoomLensScale + "px";
zoomLens.style.height = customZoom.height / scale * zoomLensScale + "px";
}
setup();
return {
cleanup: () => {
controller2.abort();
container.contains(zoomLens) && container.removeChild(zoomLens);
if (zoomTarget && zoomTarget.contains(zoomedImgWrapper)) {
zoomTarget.removeChild(zoomedImgWrapper);
return;
}
container.contains(zoomedImgWrapper) && container.removeChild(zoomedImgWrapper);
},
subscribe: store.subscribe,
getState: store.getState,
setState: (newState) => {
store.setState(newState);
}
};
}
// src/createZoomImageMove.ts
function createZoomImageMove(container, options = {}) {
let activePointerId = null;
const sourceImgElement = getSourceImage(container);
const finalOptions = {
zoomFactor: options.zoomFactor ?? 4,
zoomImageSource: options.zoomImageSource ?? sourceImgElement.src,
disabledContextMenu: options.disabledContextMenu ?? false
};
const { disabledContextMenu, zoomFactor, zoomImageSource } = finalOptions;
const store = f({
zoomedImgStatus: "idle"
});
const zoomedImg = document.createElement("img");
options.zoomImageProps?.alt && (zoomedImg.alt = options.zoomImageProps.alt);
options.zoomImageProps?.className && (zoomedImg.className = options.zoomImageProps.className);
zoomedImg.style.maxWidth = "none";
zoomedImg.style.position = "absolute";
zoomedImg.style.top = "0";
zoomedImg.style.left = "0";
if (disabledContextMenu) {
zoomedImg.style["-webkit-user-select"] = "none";
zoomedImg.style["-webkit-touch-callout"] = "none";
zoomedImg.oncontextmenu = () => false;
}
const checkValidPointer = (event) => {
return activePointerId && event.pointerId === activePointerId;
};
function handlePointerEnter(event) {
if (activePointerId === null) {
activePointerId = event.pointerId;
container.appendChild(zoomedImg);
zoomedImg.style.display = "block";
const zoomedImgWidth = sourceImgElement.clientWidth * zoomFactor;
const zoomedImgHeight = sourceImgElement.clientHeight * zoomFactor;
zoomedImg.style.width = `${zoomedImgWidth}px`;
zoomedImg.style.height = `${zoomedImgHeight}px`;
imageLoader.createZoomImage(zoomedImg, zoomImageSource, store);
processZoom(event);
event.pointerType !== "mouse" && disableScroll();
}
}
function handlePointerMove(event) {
if (checkValidPointer(event)) {
processZoom(event);
}
}
function resetZoomedImg(event) {
if (checkValidPointer(event)) {
container.contains(zoomedImg) && container.removeChild(zoomedImg);
zoomedImg.style.display = "none";
zoomedImg.style.transform = "none";
enableScroll();
activePointerId = null;
}
}
const calculatePositionX = (newPositionX) => {
const width = container.clientWidth;
if (newPositionX > 0)
return 0;
if (newPositionX + width * zoomFactor < width)
return -width * (zoomFactor - 1);
return newPositionX;
};
const calculatePositionY = (newPositionY) => {
const height = container.clientHeight;
if (newPositionY > 0)
return 0;
if (newPositionY + height * zoomFactor < height)
return -height * (zoomFactor - 1);
return newPositionY;
};
function processZoom(event) {
zoomedImg.style.display = "block";
const containerRect = container.getBoundingClientRect();
const zoomPointX = event.clientX - containerRect.left;
const zoomPointY = event.clientY - containerRect.top;
const currentPositionX = calculatePositionX(-zoomPointX * zoomFactor + zoomPointX);
const currentPositionY = calculatePositionY(-zoomPointY * zoomFactor + zoomPointY);
zoomedImg.style.transform = `translate(${currentPositionX}px, ${currentPositionY}px)`;
}
const controller2 = new AbortController();
const { signal: signal2 } = controller2;
container.addEventListener("pointerenter", handlePointerEnter, { signal: signal2 });
container.addEventListener("pointermove", handlePointerMove, { signal: signal2 });
container.addEventListener("pointerleave", resetZoomedImg, { signal: signal2 });
container.addEventListener(
"touchstart",
(event) => {
disabledContextMenu && event.preventDefault();
},
{ signal: signal2 }
);
return {
cleanup: () => {
controller2.abort();
container.contains(zoomedImg) && container.removeChild(zoomedImg);
store.cleanup();
},
subscribe: store.subscribe,
getState: store.getState
};
}
// src/createZoomImageWheel.ts
var ZOOM_DELTA = 0.5;
var defaultInitialState = {
currentZoom: 1,
enable: true,
currentPositionX: 0,
currentPositionY: 0,
currentRotation: 0
};
var defaultShouldZoomOnSingleTouch = () => true;
function createZoomImageWheel(container, options = {}) {
const sourceImgElement = getSourceImage(container);
const finalOptions = {
maxZoom: options.maxZoom || 4,
wheelZoomRatio: options.wheelZoomRatio || 0.1,
dblTapAnimationDuration: options.dblTapAnimationDuration || 300,
initialState: { ...defaultInitialState, ...options.initialState },
shouldZoomOnSingleTouch: options.shouldZoomOnSingleTouch || defaultShouldZoomOnSingleTouch
};
const store = f(finalOptions.initialState);
const checkDimensionSwitched = () => {
return [90, 270].includes(store.getState().currentRotation % 360);
};
const calculatePositionX = (newPositionX, currentZoom) => {
if (newPositionX > 0)
return 0;
const width = container.clientWidth;
if (newPositionX + width * currentZoom < width)
return -width * (currentZoom - 1);
return newPositionX;
};
const calculatePositionY = (newPositionY, currentZoom) => {
if (newPositionY > 0)
return 0;
const height = container.clientHeight;
if (newPositionY + height * currentZoom < height)
return -height * (currentZoom - 1);
return newPositionY;
};
const updateStateOnNewZoom = (currentZoom) => {
const zoomPointX = container.clientWidth / 2;
const zoomPointY = container.clientHeight / 2;
const isDimensionSwitched = checkDimensionSwitched();
const currentState = store.getState();
const zoomX = isDimensionSwitched ? currentState.currentPositionY : currentState.currentPositionX;
const zoomY = isDimensionSwitched ? currentState.currentPositionX : currentState.currentPositionY;
const zoomTargetX = (zoomPointX - zoomX) / currentState.currentZoom;
const zoomTargetY = (zoomPointY - zoomY) / currentState.currentZoom;
store.setState({
currentZoom,
currentPositionX: calculatePositionX(-zoomTargetX * currentZoom + zoomPointX, currentZoom),
currentPositionY: calculatePositionY(-zoomTargetY * currentZoom + zoomPointY, currentZoom)
});
};
let prevTwoPositions = null;
let enabledScroll = true;
const pointerMap = /* @__PURE__ */ new Map();
let lastPositionX = 0;
let lastPositionY = 0;
let startX = 0;
let startY = 0;
container.style.overflow = "hidden";
sourceImgElement.style.transformOrigin = "0 0";
function updateZoom() {
const currentState = store.getState();
sourceImgElement.style.transform = `translate(${currentState.currentPositionX}px, ${currentState.currentPositionY}px) scale(${currentState.currentZoom})`;
container.style.rotate = `${currentState.currentRotation}deg`;
}
function setState(newState) {
store.batch(() => {
const currentState = store.getState();
if (typeof newState.enable === "boolean" && newState.enable !== currentState.enable) {
store.setState({
enable: newState.enable
});
if (!newState.enable) {
return;
}
}
if (typeof newState.currentRotation === "number") {
const newCurrentRotation = newState.currentRotation;
store.setState({
currentRotation: newCurrentRotation
});
}
if (typeof newState.currentZoom === "number" && newState.currentZoom !== currentState.currentZoom) {
const newCurrentZoom = clamp(newState.currentZoom, 1, finalOptions.maxZoom);
if (newCurrentZoom === currentState.currentZoom) {
return;
}
updateStateOnNewZoom(newCurrentZoom);
}
});
updateZoom();
}
function processZoomWheel({ delta, x, y }) {
const containerRect = container.getBoundingClientRect();
const currentState = store.getState();
const isDimensionSwitched = checkDimensionSwitched();
let zoomPointX = -1;
let zoomPointY = -1;
switch (currentState.currentRotation % 360) {
case 0:
zoomPointX = x - containerRect.left;
zoomPointY = y - containerRect.top;
break;
case 90:
zoomPointX = Math.abs(x - containerRect.right);
zoomPointY = Math.abs(y - containerRect.top);
break;
case 180:
zoomPointX = Math.abs(x - containerRect.right);
zoomPointY = Math.abs(y - containerRect.bottom);
break;
case 270:
zoomPointX = Math.abs(x - containerRect.left);
zoomPointY = Math.abs(y - containerRect.bottom);
break;
}
const zoomX = isDimensionSwitched ? currentState.currentPositionY : currentState.currentPositionX;
const zoomY = isDimensionSwitched ? currentState.currentPositionX : currentState.currentPositionY;
const zoomTargetX = (zoomPointX - zoomX) / currentState.currentZoom;
const zoomTargetY = (zoomPointY - zoomY) / currentState.currentZoom;
const newCurrentZoom = clamp(
currentState.currentZoom + delta * finalOptions.wheelZoomRatio * currentState.currentZoom,
1,
finalOptions.maxZoom
);
const newX = calculatePositionX(-zoomTargetX * newCurrentZoom + zoomPointX, newCurrentZoom);
const newY = calculatePositionY(-zoomTargetY * newCurrentZoom + zoomPointY, newCurrentZoom);
store.setState({
currentZoom: newCurrentZoom,
currentPositionX: isDimensionSwitched ? newY : newX,
currentPositionY: isDimensionSwitched ? newX : newY
});
}
function updatePositionsForSinglePointerFlow() {
if (pointerMap.size === 1) {
const { x, y } = pointerMap.values().next().value;
const isDimensionSwitched = checkDimensionSwitched();
startX = isDimensionSwitched ? y : x;
startY = isDimensionSwitched ? x : y;
}
const currentState = store.getState();
lastPositionX = currentState.currentPositionX;
lastPositionY = currentState.currentPositionY;
}
function _handleWheel(event) {
event.preventDefault();
if (store.getState().currentZoom === finalOptions.maxZoom && event.deltaY < 0) {
return;
}
const delta = -clamp(event.deltaY, -ZOOM_DELTA, ZOOM_DELTA);
processZoomWheel({ delta, x: event.clientX, y: event.clientY });
updateZoom();
updatePositionsForSinglePointerFlow();
}
function _handlePointerMove(event) {
event.preventDefault();
const { clientX, clientY, pointerId } = event;
for (const [cachedPointerId] of pointerMap.entries()) {
if (cachedPointerId === pointerId) {
pointerMap.set(cachedPointerId, { x: clientX, y: clientY });
}
}
const { currentZoom, currentRotation } = store.getState();
if (pointerMap.size === 1 && currentZoom !== 1) {
const isDimensionSwitched = checkDimensionSwitched();
const normalizedClientX = isDimensionSwitched ? clientY : clientX;
const normalizedClientY = isDimensionSwitched ? clientX : clientY;
let offsetX = -1;
let offsetY = -1;
switch (currentRotation % 360) {
case 0:
offsetX = normalizedClientX - startX;
offsetY = normalizedClientY - startY;
break;
case 90:
offsetX = normalizedClientX - startX;
offsetY = startY - normalizedClientY;
break;
case 180:
offsetX = startX - normalizedClientX;
offsetY = startY - normalizedClientY;
break;
case 270:
offsetX = startX - normalizedClientX;
offsetY = normalizedClientY - startY;
break;
}
store.setState({
currentPositionX: calculatePositionX(lastPositionX + offsetX, currentZoom),
currentPositionY: calculatePositionY(lastPositionY + offsetY, currentZoom)
});
updateZoom();
}
}
const animationState = {
startTimestamp: null,
// the state at the start of the zoom animation
start: { x: 0, y: 0, zoom: 0 },
// the target state at the end of the zoom animation
target: { x: 0, y: 0, zoom: 0 }
};
function animateZoom(touchCoordinate) {
const currentState = store.getState();
animationState.startTimestamp = null;
animationState.start = {
x: currentState.currentPositionX,
y: currentState.currentPositionY,
zoom: currentState.currentZoom
};
if (currentState.currentZoom > 1) {
animationState.target = {
x: 0,
y: 0,
zoom: 1
};
} else {
animationState.target = {
zoom: finalOptions.maxZoom,
x: touchCoordinate.x * (1 - finalOptions.maxZoom),
y: touchCoordinate.y * (1 - finalOptions.maxZoom)
};
}
function lerp(a, b, t) {
return a * (1 - t) + b * t;
}
function frame(timestamp) {
if (animationState.startTimestamp === null) {
animationState.startTimestamp = timestamp;
}
let t = (timestamp - animationState.startTimestamp) / finalOptions.dblTapAnimationDuration;
if (t > 1) {
t = 1;
}
store.setState({
currentPositionX: lerp(animationState.start.x, animationState.target.x, t),
currentPositionY: lerp(animationState.start.y, animationState.target.y, t),
currentZoom: lerp(animationState.start.zoom, animationState.target.zoom, t)
});
updateZoom();
if (t < 1) {
requestAnimationFrame(frame);
}
}
requestAnimationFrame(frame);
}
let touchTimer = null;
const durationBetweenTap = 300;
function _handleTouchStart(event) {
if (event.touches.length > 1) {
return;
}
if (touchTimer === null) {
touchTimer = setTimeout(() => {
touchTimer = null;
}, durationBetweenTap);
} else {
clearTimeout(touchTimer);
touchTimer = null;
const rect = container.getBoundingClientRect();
const touch = event.touches[0];
animateZoom({
x: touch.clientX - rect.left,
y: touch.clientY - rect.top
});
return;
}
}
function _handleTouchMove(event) {
if (finalOptions.shouldZoomOnSingleTouch())
event.preventDefault();
if (event.touches.length > 1) {
event.preventDefault();
const currentTwoPositions = [...event.touches].map((t) => ({ x: t.clientX, y: t.clientY }));
if (prevTwoPositions !== null) {
const { scale, center } = computeZoomGesture(prevTwoPositions, currentTwoPositions);
processZoomWheel({ delta: Math.log(scale) / finalOptions.wheelZoomRatio, ...center });
}
prevTwoPositions = currentTwoPositions;
updateZoom();
return;
}
}
function _handlePointerDown(event) {
if (event.pointerType === "touch" && !finalOptions.shouldZoomOnSingleTouch())
return;
event.preventDefault();
if (pointerMap.size === 2) {
return;
}
if (enabledScroll) {
disableScroll();
enabledScroll = false;
}
const { clientX, clientY, pointerId } = event;
const currentState = store.getState();
lastPositionX = currentState.currentPositionX;
lastPositionY = currentState.currentPositionY;
const isDimensionSwitched = checkDimensionSwitched();
startX = isDimensionSwitched ? clientY : clientX;
startY = isDimensionSwitched ? clientX : clientY;
pointerMap.set(pointerId, { x: clientX, y: clientY });
}
function _handlePointerUp(event) {
event.preventDefault();
pointerMap.delete(event.pointerId);
if (pointerMap.size < 2) {
prevTwoPositions = null;
}
if (pointerMap.size === 0 && !enabledScroll) {
enableScroll();
enabledScroll = true;
}
updatePositionsForSinglePointerFlow();
}
function _handlePointerLeave(event) {
event.preventDefault();
pointerMap.delete(event.pointerId);
prevTwoPositions = null;
if (!enabledScroll) {
enableScroll();
enabledScroll = true;
}
}
function checkZoomEnabled() {
return store.getState().enable;
}
const handleWheel = makeMaybeCallFunction(checkZoomEnabled, _handleWheel);
const handlePointerDown = makeMaybeCallFunction(checkZoomEnabled, _handlePointerDown);
const handlePointerLeave = makeMaybeCallFunction(checkZoomEnabled, _handlePointerLeave);
const handlePointerMove = makeMaybeCallFunction(checkZoomEnabled, _handlePointerMove);
const handlePointerUp = makeMaybeCallFunction(checkZoomEnabled, _handlePointerUp);
const handleTouchStart = makeMaybeCallFunction(checkZoomEnabled, _handleTouchStart);
const handleTouchMove = makeMaybeCallFunction(checkZoomEnabled, _handleTouchMove);
const controller2 = new AbortController();
const { signal: signal2 } = controller2;
container.addEventListener("wheel", handleWheel, { signal: signal2 });
container.addEventListener("touchstart", handleTouchStart, { signal: signal2 });
container.addEventListener("touchmove", handleTouchMove, { signal: signal2 });
container.addEventListener("pointerdown", handlePointerDown, { signal: signal2 });
container.addEventListener("pointerleave", handlePointerLeave, { signal: signal2 });
container.addEventListener("pointermove", handlePointerMove, { signal: signal2 });
container.addEventListener("pointerup", handlePointerUp, { signal: signal2 });
container.addEventListener(
"touchend",
() => {
enabledScroll = true;
enableScroll();
},
{ signal: signal2 }
);
if (store.getState().currentZoom !== defaultInitialState.currentZoom) {
updateStateOnNewZoom(store.getState().currentZoom);
updateZoom();
}
return {
cleanup() {
controller2.abort();
store.cleanup();
},
subscribe: store.subscribe,
setState,
getState: store.getState
};
}
// src/createZoomImageClick.ts
function createZoomImageClick(container, options = {}) {
const sourceImgElement = getSourceImage(container);
const finalOptions = {
zoomFactor: options.zoomFactor ?? 4,
zoomImageSource: options.zoomImageSource ?? sourceImgElement.src,
disableScrollLock: options.disableScrollLock ?? false
};
const { zoomFactor, zoomImageSource, disableScrollLock } = finalOptions;
let isOnMove = false;
const store = f({
zoomedImgStatus: "idle"
});
const zoomedImgWidth = sourceImgElement.clientWidth * zoomFactor;
const zoomedImgHeight = sourceImgElement.clientHeight * zoomFactor;
const zoomedImg = container.appendChild(document.createElement("img"));
zoomedImg.alt = options.zoomImageProps?.alt || "";
zoomedImg.style.maxWidth = "none";
zoomedImg.style.display = "none";
zoomedImg.style.width = `${zoomedImgWidth}px`;
zoomedImg.style.height = `${zoomedImgHeight}px`;
zoomedImg.style.position = "absolute";
zoomedImg.style.top = "0";
zoomedImg.style.left = "0";
function handlePointerMove(event) {
if (!isOnMove) {
return;
}
processZoom(event);
}
const calculatePositionX = (newPositionX) => {
const width = container.clientWidth;
if (newPositionX > 0)
return 0;
if (newPositionX + width * zoomFactor < width)
return -width * (zoomFactor - 1);
return newPositionX;
};
const calculatePositionY = (newPositionY) => {
const height = container.clientHeight;
if (newPositionY > 0)
return 0;
if (newPositionY + height * zoomFactor < height)
return -height * (zoomFactor - 1);
return newPositionY;
};
function processZoom(event) {
zoomedImg.style.display = "block";
imageLoader.createZoomImage(zoomedImg, zoomImageSource, store);
const containerRect = container.getBoundingClientRect();
const zoomPointX = event.clientX - containerRect.left;
const zoomPointY = event.clientY - containerRect.top;
const currentPositionX = calculatePositionX(-zoomPointX * zoomFactor + zoomPointX);
const currentPositionY = calculatePositionY(-zoomPointY * zoomFactor + zoomPointY);
zoomedImg.style.transform = `translate(${currentPositionX}px, ${currentPositionY}px)`;
}
function handlePointerDown(event) {
if (isOnMove) {
isOnMove = false;
zoomedImg.style.display = "none";
return;
}
processZoom(event);
isOnMove = true;
}
const controller2 = new AbortController();
const { signal: signal2 } = controller2;
container.addEventListener("pointerdown", handlePointerDown, { signal: signal2 });
container.addEventListener("pointerenter", disableScrollLock ? noop : disableScroll, { signal: signal2 });
container.addEventListener("pointerleave", disableScrollLock ? noop : enableScroll, { signal: signal2 });
container.addEventListener("pointermove", handlePointerMove, { signal: signal2 });
container.addEventListener("touchend", enableScroll, { signal: signal2 });
return {
cleanup: () => {
controller2.abort();
container.contains(zoomedImg) && container.removeChild(zoomedImg);
store.cleanup();
},
subscribe: store.subscribe,
getState: store.getState
};
}
// src/makeCalculatePercentage.ts
var makeCalculatePercentage = (maxZoom) => scaleLinear({
domainStart: 1,
domainStop: maxZoom,
rangeStart: 0,
rangeStop: 100
});
// src/makeCalculateZoom.ts
var makeCalculateZoom = (maxZoom) => scaleLinear({
domainStart: 0,
domainStop: 100,
rangeStart: 1,
rangeStop: maxZoom
});
// src/cropImage.ts
var cropImage = async ({ image, positionX, positionY, currentZoom, rotation = 0 }) => {
const canvas = document.createElement("canvas");
const scale = image.naturalWidth / (image.clientWidth * currentZoom);
const normalizedRotation = rotation % 360;
const croppedImageWidth = image.clientWidth * scale;
const croppedImageHeight = image.clientHeight * scale;
canvas.width = croppedImageWidth;
canvas.height = croppedImageHeight;
const canvasContext = canvas.getContext("2d");
const sx = Math.max(0, Math.abs(positionX) * scale);
const sy = Math.max(0, Math.abs(positionY) * scale);
canvasContext.drawImage(
image,
sx,
sy,
croppedImageWidth,
croppedImageHeight,
0,
0,
croppedImageWidth,
croppedImageHeight
);
const originalImage = new Image();
originalImage.src = canvas.toDataURL();
await new Promise((resolve) => setTimeout(resolve, 0));
const rotatedCanvas = document.createElement("canvas");
const rotatedCanvasContext = rotatedCanvas.getContext("2d");
if (normalizedRotation === 90 || normalizedRotation === 270) {
rotatedCanvas.width = originalImage.naturalHeight;
rotatedCanvas.height = originalImage.naturalWidth;
} else {
rotatedCanvas.width = originalImage.naturalWidth;
rotatedCanvas.height = originalImage.naturalHeight;
}
rotatedCanvasContext.clearRect(0, 0, canvas.width, canvas.height);
if (normalizedRotation === 90 || normalizedRotation === 270) {
rotatedCanvasContext.translate(originalImage.height / 2, originalImage.width / 2);
} else {
rotatedCanvasContext.translate(originalImage.width / 2, originalImage.height / 2);
}
rotatedCanvasContext.rotate(normalizedRotation * Math.PI / 180);
rotatedCanvasContext.drawImage(originalImage, -originalImage.width / 2, -originalImage.height / 2);
return rotatedCanvas.toDataURL();
};
exports.createZoomImageClick = createZoomImageClick;
exports.createZoomImageHover = createZoomImageHover;
exports.createZoomImageMove = createZoomImageMove;
exports.createZoomImageWheel = createZoomImageWheel;
exports.cropImage = cropImage;
exports.makeCalculatePercentage = makeCalculatePercentage;
exports.makeCalculateZoom = makeCalculateZoom;
return exports;
})({});
//# sourceMappingURL=out.js.map
//# sourceMappingURL=index.global.js.map