Search code examples
javascriptimagemathfrontendzooming

How to create a pan & zoom effect for an image with different proportions to its container?


I have the following simple code that pans and zooms an image (slightly modified from [here]:(https://www.cssscript.com/image-zoom-hover-effect/)

    function imageZoom(container, img) {

      container.addEventListener("mousemove", onZoom);
      container.addEventListener("mouseover", onZoom);
      container.addEventListener("mouseleave", offZoom);
      function onZoom(e) {
    
        let offset = container.getBoundingClientRect();
          const x = e.clientX - offset.left;
          const y = e.clientY - offset.top;
          img.style.transformOrigin = `${x}px ${y}px`;
          img.style.transform = "scale(2.5)";
      }
      function offZoom(e) {
          img.style.transformOrigin = `center center`;
          img.style.transform = "scale(1)";
      }
    }

This works well when the image is proportional to container. I need it to work with random-sized images inside square containers, with the ability to show the entire image via pan & zoom.

I've tried multiplying X and Y by

        let coefX= img.naturalWidth / container.offsetWidth
        let coefY= img.naturalHeight / container.offsetHeight

respectively, but that resulted in the image leaving the container altogether. I feel like the math is simple and obvious, but I can't seem to grasp it - other attempts I've made resulted in similar problems.


Solution

  • I always wanted to make that effect. I "found" this online. This solution features only pan, no zoom. With an added bonus of a safe zone around the frame.

    // container, image are defined
    
    function panImage(event) {
    
      // Adjust this value to control the buffer area around the frame
      const buffer = 10; 
    
      const containerRect = container.getBoundingClientRect();
    
      const mouseX = event.clientX - containerRect.left;
      const mouseY = event.clientY - containerRect.top;
    
      const maxX = image.clientWidth - container.clientWidth;
      const maxY = image.clientHeight - container.clientHeight;
    
      const xPercentage = (mouseX - buffer) / (container.clientWidth - buffer * 2);
      const yPercentage = (mouseY - buffer) / (container.clientHeight - buffer * 2);
    
      const offsetX = Math.min(Math.max(0, xPercentage * maxX), maxX);
      const offsetY = Math.min(Math.max(0, yPercentage * maxY), maxY);
    
      image.style.transform = `translate(-${offsetX}px, -${offsetY}px)`;
    }
    #container {
      width: 200px;
      height: 150px;
      overflow: hidden;
      border: 1px solid red;
    }
    
    #image {
      transform: translate(0, 0);
      transition: all 0.25s ease;
    }
    <div id="container" onmousemove="panImage(event)">
      <img id="image" src="https://picsum.photos/412/370">
    </div>