I am required to implement a pan and zoom feature for an image, my project is implemented from React, due to performance concerns, I couldn't use normal useState and other APIs because of performance concerns. I went along with a vanillaJS code inspired from
https://github.com/kwdowik/zoom-pan/blob/master/src/renderer.js
My problem is when doing the zoom out,
export const hasPositionChanged = ({ pos, prevPos }: Position): boolean =>
pos !== prevPos;
const valueInRange = ({ minScale, maxScale, scale }: ScaleRange): boolean =>
scale <= maxScale && scale >= minScale;
const getTranslate =
({ minScale, maxScale, scale }: ScaleRange) =>
({ pos, prevPos, translate }: TranslateParams): number =>
valueInRange({ minScale, maxScale, scale }) &&
hasPositionChanged({ pos, prevPos })
? translate + (pos - prevPos * scale) * (1 - 1 / scale)
: translate;
const getScale = ({
scale,
minScale,
maxScale,
scaleSensitivity,
deltaScale,
}: ScaleParams): [number, number] => {
let newScale = scale + deltaScale / (scaleSensitivity / scale);
newScale = Math.max(minScale, Math.min(newScale, maxScale));
return [scale, newScale];
};
const getMatrix = ({ scale, translateX, translateY }: MatrixParams): string =>
`matrix(${scale}, 0, 0, ${scale}, ${translateX}, ${translateY})`;
const makeZoom = (state: RendererState): ZoomActions => ({
zoom: ({ x, y, deltaScale }: ZoomParams) => {
const { left, top } = state.element.getBoundingClientRect();
const { minScale, maxScale, scaleSensitivity } = state;
const [scale, newScale] = getScale({
scale: state.transformation.scale,
deltaScale,
minScale,
maxScale,
scaleSensitivity,
});
const originX = x - left;
const originY = y - top;
const newOriginX = originX / scale;
const newOriginY = originY / scale;
const translate = getTranslate({ scale, minScale, maxScale });
let translateX = translate({
pos: originX,
prevPos: state.transformation.originX,
translate: state.transformation.translateX,
});
let translateY = translate({
pos: originY,
prevPos: state.transformation.originY,
translate: state.transformation.translateY,
});
const transformOrigin = `${newOriginX}px ${newOriginY}px`;
const transform = getMatrix({
scale: newScale,
translateX,
translateY,
});
state.element.style.transformOrigin = transformOrigin;
state.element.style.transform = transform;
if (state.canvasElement) {
state.canvasElement.style.transformOrigin = transformOrigin;
state.canvasElement.style.transform = transform;
}
state.transformation = {
originX: newOriginX,
originY: newOriginY,
translateX,
translateY,
scale: newScale,
};
},
});
export const renderer = ({
minScale,
maxScale,
element,
canvasElement,
scaleSensitivity = 0.1,
}: RendererParams): RendererInstance => {
const state: RendererState = {
element,
canvasElement,
minScale,
maxScale,
scaleSensitivity,
transformation: {
originX: 0,
originY: 0,
translateX: 0,
translateY: 0,
scale: 1,
},
};
if (canvasElement) {
canvasElement.style.zIndex = "10";
}
return Object.assign({}, makeZoom(state));
};
you can view full code from the link above.
As you can observe
const originX = x - left;
const originY = y - top;
const newOriginX = originX / scale;
const newOriginY = originY / scale;
the new origin is calculated from the scale as well the mouse position and image position difference, when the image is zooming out, the value of scale becomes so small that the new origin values becomes very large, making the image to have unexpected translated positions. I went with transform matrix because I wanted to do the pan as well (the code is not included above), but my problem is with the origin calculation.
I found out using a clamp function is a solution, but it didn't worked out for though.
If there are any suggestions, those would be most thankful.