javascript - How to calculate the new transform origin when translate is applied - Stack Overflow

admin2025-04-22  1

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)
    });
  });
}`

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)
    });
  });
}`
Share Improve this question edited Jan 21 at 16:13 WesCritch asked Jan 21 at 15:08 WesCritchWesCritch 316 bronze badges 3
  • Can we see your code for zoom_control? I suspect the issue lies within. – Grinn Commented Jan 21 at 15:49
  • I am half thinking it's something to do with that part too, I've added in the code in the original question – WesCritch Commented Jan 21 at 16:11
  • @Grinn Having looked after taking a bit of a break would it be more wise to change the target attr in the smoothOriginChange function and make it the overall body or would there be a more consise way without bloating the code out? – WesCritch Commented Jan 21 at 20:55
Add a comment  | 

2 Answers 2

Reset to default 1

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}`
      });
   });
}

I ended up pretty much rewriting the thing from scratch. It's not perfect, but hopefully it helps. Just because it made it easier to test, I also added drag & drop capabilities, which you can feel free to remove.

The only known issue is that it jumps a bit when you first put your fingers on the screen to start zooming. It jumps more relative to the distance between your current and last touch points. It also might be respective of the scale.

let startDistance = 0;
let scale = 1;
let lastScale = 1;
let isDragging = false;
let lastX = 0,
lastY = 0;
//const image = document.getElementById("container");
const image = document.getElementById("image");

// Magic number for dragging, specific to my display. You may need to adjust this.
const dragScale = 2.5;

// Set a low animation duration, just to be safe.
gsap.defaults({ duration: 0.1 });

// Calculates the distance between two touch points.
function getDistance(touch1, touch2) {
return Math.sqrt(
    Math.pow(touch2.clientX - touch1.clientX, 2) +
    Math.pow(touch2.clientY - touch1.clientY, 2)
);
}

// Calculates the center between two touch points.
function getMidpoint(touch1, touch2) {
return {
    x: (touch1.clientX + touch2.clientX) / 2,
    y: (touch1.clientY + touch2.clientY) / 2
};
}

// Touchstart event to detect initial pinch or drag
image.addEventListener("touchstart", (event) => {
event.preventDefault();
console.log('scale', scale);
if (event.touches.length === 2) {
    console.log('start zooming');
    startDistance = getDistance(event.touches[0], event.touches[1]);
    const midpoint = getMidpoint(event.touches[0], event.touches[1]);
    gsap.set(image, { transformOrigin: `${midpoint.x}px ${midpoint.y}px` });
} else if (event.touches.length === 1) {
    console.log('start dragging');
    isDragging = true;
    lastX = event.touches[0].clientX;
    lastY = event.touches[0].clientY;
}
});

// Touchmove event to handle pinch-to-zoom or drag
image.addEventListener("touchmove", (event) => {
event.preventDefault();
if (event.touches.length === 2) {
    console.log('is zooming');
    event.preventDefault();
    const currentDistance = getDistance(event.touches[0], event.touches[1]);
    const scaleChange = currentDistance / startDistance;
    // Base scale isn't updated until touchend, otherwise things get weird.
    lastScale = scale * scaleChange;
    gsap.to(image, {
    scale: lastScale
    });
} else if (event.touches.length === 1 && isDragging) {
    console.log('is dragging');
    const dx = (event.touches[0].clientX - lastX)*dragScale;
    const dy = (event.touches[0].clientY - lastY)*dragScale;
    lastX = event.touches[0].clientX;
    lastY = event.touches[0].clientY;
    gsap.to(image, {
    x: `+=${dx}`,
    y: `+=${dy}`
    });
}
});

// Update scale after zooming or end dragging
image.addEventListener("touchend", (event) => {
if (event.touches.length < 2) {
    // No longer zooming, update the base scale.
    scale = lastScale;
}
isDragging = event.touches.length !== 0;
});

Let me know if you have any questions. You can also run it as a CodePen.

转载请注明原文地址:http://anycun.com/QandA/1745301711a90586.html