0

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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.