Search code examples
javascriptgsap

How to calculate the new transform origin when translate is applied


I've created a touch event to be able to zoom into an image, saved the x and y values in the touchstart

imageContainer.addEventListener("touchstart", function (event) {
   event.preventDefault();
   if (event.touches.length === 2) {

      let touch1s = [event.touches[0].pageX, event.touches[0].pageY];
      let touch2s = [event.touches[1].pageX, event.touches[1].pageY];

      x = Math.abs(touch1s[0] + touch2s[0]) / 2;
      y = Math.abs(touch1s[1] + touch2s[1]) / 2;

      originalDistance = Math.hypot(event.touches[0].pageX - event.touches[1].pageX,event.touches[0].pageY - event.touches[1].pageY );

      zoomDrag[0].disable();
   }
});

In the touchmove event listener I have added in functionality to be able to zoom in and out on the point of my finger,

imageContainer.addEventListener("touchmove", function (event) {
   event.preventDefault();
   if (event.touches.length === 2) {
      let currentDistance = Math.hypot(event.touches[0].pageX - event.touches[1].pageX,event.touches[0].pageY - event.touches[1].pageY);
 
      if (originalDistance < currentDistance) {
         if (gsap.getProperty(imageContainer, "scale") <= maxScale) {
            zoom_control(imageContainer, x, y, "+=0.4");
         }
      } else if (originalDistance > currentDistance) {
         if (gsap.getProperty(imageContainer, "scale") >= minScale) {
            zoom_control(imageContainer, x, y, "-=0.4");
         } 
      }
   }
});

However when I implement the GSAP Draggable library and perform a drag when I am zoomed in, and try to zoom back in/out, the transform origin isn't where it should be.

Math isn't my strong suit so what would the formula be to make the transform origin correct?

Here is the code for zoom_control for those asking

function zoom_control(item, posX, posY, scale) {
  if (!isNaN(posX) && !isNaN(posY)) {
    smoothOriginChange(item, `${posX}px ${posY}px`);
  }
  gsap.to(item, {
    scale: scale
  });
}

function smoothOriginChange(targets, transformOrigin) {
  gsap.utils.toArray(targets).forEach(function (target) {
    let before = target.getBoundingClientRect();
    gsap.set(target, { transformOrigin: transformOrigin });
    let after = target.getBoundingClientRect();
    gsap.set(target, {
      x: "+=" + (before.left - after.left),
      y: "+=" + (before.top - after.top)
    });
  });
}`

Solution

  • Okay so whilst Grin's method works and can get the job done, I resorted to using the HammerJS library. Whilst it has known bugs and hasn't been updated for a while, it gets the job done. The finished product can be seen in the working demo

    https://codepen.io/wescritch98/pen/wBwmvmm
    

    But the code usage has been significantly reduced in my js file.

    let hammer = new Hammer(imageContainer),

    hammer.get("pinch").set({ enable: true });
    
    hammer.on("pinchstart", (e) => {
       //transform X and Y are defines by the touch events offset X and Y values
       transformX = e.srcEvent.offsetX;
       transformY = e.srcEvent.offsetY;
    
       // We disable the Draggable functionality to avoid the jittery look when pinching with 2 or more fingers
       zoomDrag[0].disable();
    });
    
    hammer.on("pinch", (e) => {
    
       switch (e.additionalEvent) {
          // The pinch out is the zooming in functionality
          case "pinchout":
             /*
             * Will stop the image going above whatever maxScale number we have applied
             * Otherwise the scale will default to the maxScale
             */
             if (gsap.getProperty(imageContainer, "scale") <= maxScale) {
                zoom_control(imageContainer, transformX, transformY, "+=0.4");
             } else {
                gsap.to(imageContainer, {
                   scale: maxScale
                });
             }
             break;
          // The pinch in is the zooming out functionality
          case "pinchin":
             /*
             * Will stop the image going above whatever maxScale number we have applied
             * Otherwise the scale will default to the maxScale
             */
             if (gsap.getProperty(imageContainer, "scale") >= minScale) {
                zoom_control(imageContainer, transformX, transformY, "-=0.4");
             } else {
                gsap.to(imageContainer, {
                   scale: minScale
                });
             }
             break;
       }
    });
    
    hammer.on("pinchend", () => {
       // We reenable the Draggable event 
       zoomDrag[0].enable();
    });
    
    function zoom_control(item, posX, posY, scale) {
       if (!isNaN(posX) && !isNaN(posY)) {
          smoothOriginChange(item, `${posX}px ${posY}px`);
       }
       gsap.to(item, {
          scale: scale
       });
    }
    
    // This makes the transform origin change smoothly and stop a jump every time we zoom in on a new place
    function smoothOriginChange(targets, transformOrigin) {
       gsap.utils.toArray(targets).forEach(function (target) {
          let before = target.getBoundingClientRect();
          gsap.set(target, { transformOrigin: transformOrigin });
          let after = target.getBoundingClientRect();
          gsap.set(target, {
             x: `+= ${before.left - after.left}`,
             y: `+= ${before.top - after.top}`
          });
       });
    }