Javascript css3 mousewheel zoom origin problem

I’m trying to make a zoom and drag system with javascript and css3 transform scale.
Dragging works but scaling has some problems. I would like to have so that it zooms toward the center of the mouse.

Here is my code:

<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
<meta charset="utf-8">

<viewport>
  <container>
    <img src="http://placehold.it/300x200">
    <p>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </p>
    <div style="position: absolute; top: 100px; left: 400px; width: 40px; height: 40px; background: red;"></div>
  </container>
</viewport>
<input type="text" id="zoom">
<button onclick="setScale(Number(document.getElementById('zoom').value))">Set Scale</button>
<p>
  Zoom with mousewheel and drag with middle mouseclick;
</p>

<style>

viewport {
  display: block;
  position: relative;
  height: 500px;
  background: #4c4e58;
  overflow: hidden;
}
container {
  display: block;
  position: relative;
  width: 1000px; height: 1000px;
  transform-origin: 0 0;
}

</style>

<script>

class Container {
  constructor(container) {
    this.container = container;
    this.viewport = container.parentNode;
    let rect = this.viewport.getBoundingClientRect();
    this.width = rect.width;
    this.height = rect.height;
    this.x = 0, this.y = 0;
    this.scale = 1; // default scale
    this.zoom = 1.1; // zoom factor
    this.dragging = false;

    this.init();
  }
  init() {
    this.updateStyles();
    let container = this.container;
    let viewport = this.viewport;
    let viewRect = viewport.getBoundingClientRect();

    let startPos = {x: 0, y: 0};
    viewport.addEventListener('mousedown', (e) => {
      if (e.button == 1) {
        e.preventDefault();
        this.dragging = true;
        this.updateStyles();
        let rect = container.getBoundingClientRect();
        startPos = {x: e.clientX - rect.left, y: e.clientY - rect.top};
      }
    });
    window.addEventListener('mousemove', (e) => {
      if (this.dragging) {
        this.x = e.pageX - startPos.x;
        this.y = e.pageY - startPos.y;
        this.updateStyles();
      }
    });
    window.addEventListener('mouseup', () => {
      if (this.dragging) {
        this.dragging = false;
        this.updateStyles();
      }
    });

    let onscroll = (e) => {
      e.preventDefault();
      let scale = 0;
      let wheel = e.detail || e.deltaY;
      scale = wheel > 0 ? this.scale / this.zoom : this.scale * this.zoom;
      let mouseX = (e.pageX - viewRect.left);
      let mouseY = (e.pageY - viewRect.top);
      this.setZoom(scale, mouseX, mouseY);
    };
    viewport.addEventListener('DOMMouseScroll', onscroll); // Firefox
    viewport.addEventListener('mousewheel', onscroll); // Chrome, etc.
  }
  setZoom(scale, x = this.width / 2, y = this.height / 2) {
    let oldscale = this.scale;
    this.scale = scale;
    // let scalechange = (this.scale - oldscale) / this.scale;
    let scalechange = this.scale - oldscale;
    this.scale = this.scale < .25 ? .25 : this.scale;
    this.scale = this.scale > 4 ? 4 : this.scale;
    this.x = Math.round(-(x - this.x) * scalechange + this.x);
    this.y = Math.round(-(y - this.y) * scalechange + this.y);
    this.updateStyles();
  }
  updateStyles() {
    this.container.style.left = this.x + 'px';
    this.container.style.top = this.y + 'px';
    this.container.style.transform = `scale(${this.scale})`;
    this.viewport.style.cursor = this.dragging ? 'move' : '';
  }
}

let container = new Container(document.querySelector('container'));

function setScale(scale) {
  console.log("scale", scale);
  container.setZoom(scale);
}

</script>

The code is not yet quite as good. It would be cool if someone can help me with the code.

I would like to have so that it zooms toward the center of the mouse.

Have you tried setting the transform-origin style property to the x,y of the mouse position on that element? I think that will behave better than moving the containers top / left.

The code looks decent to me!

One thing you might want to consider is making it respond to left clicks - Only PC’s have a middle mouse button. Then there’s also the touch events you could work with so it works on mobile / tablets too.

Off topic question but what are these tags?

Oh, that’s XHTML :wink:

I’ve now changed for transform-origin. Before the next scroll it reset the origin and set top, left style properties.
The first scroll works fine but the second is shifted. :confused:

class Container {
  constructor(container) {
    this.container = container;
    this.viewport = container.parentNode;
    let rect = container.getBoundingClientRect();
    this.width = rect.width;
    this.height = rect.height;
    this.x = 0, this.y = 0;
    this.origin = {x: 0, y: 0};
    this.scale = 1; // default scale
    this.zoom = 1.125; // zoom factor
    this.dragging = false;

    this.init();
  }
  init() {
    this.updateStyles();
    let container = this.container;
    let viewport = this.viewport;
    let viewRect = viewport.getBoundingClientRect();

    let startPos = {x: 0, y: 0};
    viewport.addEventListener('mousedown', (e) => {
      if (e.button == 1 || e.shiftKey) {
        e.preventDefault();
        this.dragging = true;
        this.updateStyles();
        let rect = container.getBoundingClientRect();
        startPos = {
          x: e.pageX - this.x,
          y: e.pageY - this.y
        };
      } else if (e.ctrlKey) {
        // ctrl + click to recenter the origin
        this.recenter();
        this.updateStyles();
      }
    });
    window.addEventListener('mousemove', (e) => {
      if (this.dragging) {
        this.x = e.pageX - startPos.x;
        this.y = e.pageY - startPos.y;
        this.updateStyles();
      }
    });
    window.addEventListener('mouseup', () => {
      if (this.dragging) {
        this.dragging = false;
        this.updateStyles();
      }
    });

    let onscroll = (e) => {
      e.preventDefault();
      let scale = 0;
      let wheel = e.detail || e.deltaY;
      scale = wheel > 0 ? this.scale / this.zoom : this.scale * this.zoom;
      let mouseX = (e.pageX - viewRect.left);
      let mouseY = (e.pageY - viewRect.top);
      this.setScale(scale, mouseX, mouseY);
    };
    viewport.addEventListener('DOMMouseScroll', onscroll); // Firefox
    viewport.addEventListener('mousewheel', onscroll); // Chrome, etc.
  }
  recenter() {
    // Calculate left and top property from origin and reset the origin
    this.x -= (this.origin.x * this.scale) - this.origin.x;
    this.y -= (this.origin.y * this.scale) - this.origin.y;
    this.origin = {x:0, y:0};
  }
  setScale(scale, x = this.width / 2, y = this.height / 2) {
    console.log("x, y", x, y);
    this.recenter();
    // Get relative pos from container
    x = x - this.x;
    y = y - this.y;
    this.scale = scale;
    this.scale = this.scale < .25 ? .25 : this.scale; // min zoom
    this.scale = this.scale > 4 ? 4 : this.scale; // max zoom
    this.origin = {
      x: x,
      y: y
    };
    this.updateStyles();
  }
  updateStyles() {
    this.container.style.left = this.x + 'px';
    this.container.style.top = this.y + 'px';
    this.container.style.transform = `scale(${this.scale})`;
    this.container.style.transformOrigin = `${this.origin.x}px ${this.origin.y}px`;
    this.viewport.style.cursor = this.dragging ? 'move' : '';
  }
}

df

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.