javascriptzoomingpanzoom

Panzoom inconsistent when zooming in without a mouse and keyboard buttons


I have tried the solution posted in this github post

Steps to reproduce:

  1. Click on Zoom in Button twice.
  2. When Zoom is 150%; Click zoom out.

The image zooms in but should actually zoom out

const element = document.querySelector('#scene');

const zoomLevels = [0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3];
let currentZoomLevel = zoomLevels[4];
const text = document.querySelector('#text');

let panZoomController = panzoom(element, {
  beforeWheel: function(e) {
    // allow wheel-zoom Disabled
    return true;
  }
});

const setText = (input) => {
  text.innerText = input;
}

const zoom = () => {
  const isSmooth = false;
  const scale = currentZoomLevel;
  if (scale) {
    const transform = panZoomController.getTransform();
    const deltaX = transform.x;
    const deltaY = transform.y;
    const offsetX = scale + deltaX;
    const offsetY = scale + deltaY;

    if (isSmooth) {
      panZoomController.smoothZoom(0, 0, scale);
    } else {
      panZoomController.zoomTo(offsetX, offsetY, scale);
    }
  }
};


const zoomIn = () => {
  const idx = zoomLevels.indexOf(currentZoomLevel);

  // If next element exists
  if (typeof zoomLevels[idx + 1] !== 'undefined') {
    currentZoomLevel = zoomLevels[idx + 1];
  }

  if (currentZoomLevel === 1) {
    panZoomController.moveTo(0, 0);
    panZoomController.zoomAbs(0, 0, 1);
  } else {
    zoom();
  }
  setText(currentZoomLevel * 100 + '%');

};

const zoomOut = () => {
  const idx = zoomLevels.indexOf(currentZoomLevel);

  //if previous element exists
  if (typeof zoomLevels[idx - 1] !== 'undefined') {
    currentZoomLevel = zoomLevels[idx - 1];
  }


  if (currentZoomLevel === 1) {
    panZoomController.moveTo(0, 0);
    panZoomController.zoomAbs(0, 0, 1);
  } else {
    zoom();
  }

  setText(currentZoomLevel * 100 + '%');
};
div {
  overflow: hidden;
  border: 3px solid red
}
<script src="https://unpkg.com/panzoom@8.1.0/dist/panzoom.min.js"></script>

<body>
  <div>
    <img id="scene" src="https://www.probytes.net/wp-content/uploads/2018/01/5-1.png">
  </div>

  <br/>
  <button onclick="zoomOut()">-</button>
  <span id="text">100%</span>
  <button onclick="zoomIn()">+</button>
</body>


Solution

  • You have mistaken a relative scale for an absolute one here. While zoomAbs() is using an absolute zoom level, the zoom() function is using zoomTo() which takes in a relative zoom level.

    Therefore, describing your entire process:

     - Initial.  Scale: 1
     - Zoom in.  Scale: 1 * 1.25 = 1.25
     - Zoom in.  Scale: 1.25 * 1.5 = 1.875
     - Zoom out. Scale: 1.875 * 1.25 = 2.344
     - Zoom out. Scale: Hard reset to 1
    

    Note: You can also see this happening in reverse if you zoom out more than once, then try to zoom in.

    This can be remedied by either using the absolute scale method zoomAbs() (much simpler) or by calculating the relative zoom using current and required scales.

    To do this simply replace this line

    panZoomController.zoomTo(offsetX, offsetY, scale);
    

    with

    panZoomController.zoomAbs(offsetX, offsetY, scale);
    

    Fixed snippet:

    const element = document.querySelector('#scene');
    
    const zoomLevels = [0.1, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3];
    let currentZoomLevel = zoomLevels[4];
    const text = document.querySelector('#text');
    
    let panZoomController = panzoom(element, {
      beforeWheel: function(e) {
        // allow wheel-zoom Disabled
        return true;
      }
    });
    
    const setText = (input) => {
      text.innerText = input;
    }
    
    const zoom = () => {
      const isSmooth = false;
      const scale = currentZoomLevel;
      if (scale) {
        const transform = panZoomController.getTransform();
        const deltaX = transform.x;
        const deltaY = transform.y;
        const offsetX = scale + deltaX;
        const offsetY = scale + deltaY;
    
        if (isSmooth) {
          panZoomController.smoothZoom(0, 0, scale);
        } else {
          panZoomController.zoomAbs(offsetX, offsetY, scale);
        }
      }
    };
    
    
    const zoomIn = () => {
      const idx = zoomLevels.indexOf(currentZoomLevel);
    
      // If next element exists
      if (typeof zoomLevels[idx + 1] !== 'undefined') {
        currentZoomLevel = zoomLevels[idx + 1];
      }
    
      if (currentZoomLevel === 1) {
        panZoomController.moveTo(0, 0);
        panZoomController.zoomAbs(0, 0, 1);
      } else {
        zoom();
      }
      setText(currentZoomLevel * 100 + '%');
    
    };
    
    const zoomOut = () => {
      const idx = zoomLevels.indexOf(currentZoomLevel);
    
      //if previous element exists
      if (typeof zoomLevels[idx - 1] !== 'undefined') {
        currentZoomLevel = zoomLevels[idx - 1];
      }
    
    
      if (currentZoomLevel === 1) {
        panZoomController.moveTo(0, 0);
        panZoomController.zoomAbs(0, 0, 1);
      } else {
        zoom();
      }
    
      setText(currentZoomLevel * 100 + '%');
    };
    div {
      overflow: hidden;
      border: 3px solid red
    }
    <script src="https://unpkg.com/panzoom@8.1.0/dist/panzoom.min.js"></script>
    
    <body>
      <div>
        <img id="scene" src="https://www.probytes.net/wp-content/uploads/2018/01/5-1.png">
      </div>
    
      <br/>
      <button onclick="zoomOut()">-</button>
      <span id="text">100%</span>
      <button onclick="zoomIn()">+</button>
    </body>