Skip to content
Advertisement

Javascript zoom in/out to mouse x/y coordinates

I managed to make the mouse dragging to scroll the div, but the zooming in/out with the mouse is not complete.

It works, but I would like for the mouse pointer to hold the image in that position and scale it at the same time, like so:

Screenshot

I need to use scrollBy() to return the scrolling back to the previous point before scaling. Anyone knows how to do that?

This is a fiddle made by someone https://jsfiddle.net/xta2ccdt/13/ and it’s exactly what I need, but the code uses translate() and other things which don’t apply here, since I have scrolling/dragging too.

Here’s my jsfiddle code https://jsfiddle.net/catalinu/1f6e0jna/

And here’s the code in stackoverflow:

Please help. I struggled with this for days.

for (const divMain of document.getElementsByClassName('main')) {
  // drag the section
  for (const divSection of divMain.getElementsByClassName('section')) {
  	// when mouse is pressed store the current mouse x,y
    let previousX, previousY
    divSection.addEventListener('mousedown', (event) => {
      previousX = event.pageX
      previousY = event.pageY
    })
    
    // when mouse is moved, scrollBy() the mouse movement x,y
    divSection.addEventListener('mousemove', (event) => {
    	// only do this when the primary mouse button is pressed (event.buttons = 1)
      if (event.buttons) {
        let dragX = 0
        let dragY = 0
        // skip the drag when the x position was not changed
        if (event.pageX - previousX !== 0) {
          dragX = previousX - event.pageX
          previousX = event.pageX
        }
        // skip the drag when the y position was not changed
        if (event.pageY - previousY !== 0) {
          dragY = previousY - event.pageY
          previousY = event.pageY
        }
        // scrollBy x and y
        if (dragX !== 0 || dragY !== 0) {
          divMain.scrollBy(dragX, dragY)
        }
      }
    })
  }

  // zoom in/out on the section
  let scale = 1
  const scaleFactor = 0.05
  divMain.addEventListener('wheel', (event) => {
  	// preventDefault to stop the onselectionstart event logic
    event.preventDefault()
    for (const divSection of divMain.getElementsByClassName('section')) {
    	// set the scale change value
      const scaleChange = (event.deltaY < 0) ? scaleFactor : -scaleFactor
      // don't allow the scale to go outside of [0,5 - 2]
      if (scale + scaleChange < 0.5 || scale + scaleChange > 2) {
        return
      }
      // round the value when using high dpi monitors
      scale = Math.round((scale + scaleChange) * 100) / 100

			// apply the css scale
      divSection.style.transform = `scale(${scale}, ${scale})`

      // re-adjust the scrollbars        
      const x = Math.round(divMain.scrollLeft * scaleChange)
      const y = Math.round(divMain.scrollTop * scaleChange)
      divMain.scrollBy(x, y)
    }
  })
}
body {
  margin: 0;
}

.main {
	width: 100%; /* percentage fixes the X axis white space when zoom out */
	height: 100vh; /* this is still an issue where you see white space when zoom out in the Y axis */
	overflow: scroll; /* needed for safari to show the x axis scrollbar */
}

.main .section {
	width: 200%;
	height: 200vh;
	background-image: url('https://iso.500px.com/wp-content/uploads/2014/07/big-one.jpg');
	transform-origin: 0 0;
}
<main class="main">
  <section class="section"></section>
</main>

Advertisement

Answer

You issue is mostly around the below lines

const x = Math.round(divMain.scrollLeft * scaleChange)
const y = Math.round(divMain.scrollTop * scaleChange)

The way scroll with scale works like below

  • Calculate the unscaled x, y coordinate where zoom happens
  • Calculate the new scaled x, y coordinate my multiplying it with the new scale
  • Now you want this new coordinate remain at the same place where the existing coordinate was. So basically if you subtract the offset x,y from the new scaled x,y, you get the scroll left and top.

The updated code is like below

for (const divMain of document.getElementsByClassName('main')) {
  // drag the section
  for (const divSection of divMain.getElementsByClassName('section')) {
    // when mouse is pressed store the current mouse x,y
    let previousX, previousY
    divSection.addEventListener('mousedown', (event) => {
      previousX = event.pageX
      previousY = event.pageY
    })

    // when mouse is moved, scrollBy() the mouse movement x,y
    divSection.addEventListener('mousemove', (event) => {
        // only do this when the primary mouse button is pressed (event.buttons = 1)
      if (event.buttons) {
        let dragX = 0
        let dragY = 0
        // skip the drag when the x position was not changed
        if (event.pageX - previousX !== 0) {
          dragX = previousX - event.pageX
          previousX = event.pageX
        }
        // skip the drag when the y position was not changed
        if (event.pageY - previousY !== 0) {
          dragY = previousY - event.pageY
          previousY = event.pageY
        }
        // scrollBy x and y
        if (dragX !== 0 || dragY !== 0) {
          divMain.scrollBy(dragX, dragY)
        }
      }
    })
  }

  // zoom in/out on the section
  let scale = 1
  const factor = 0.05
  const max_scale =4

  divMain.addEventListener('wheel', (e) => {
    // preventDefault to stop the onselectionstart event logic
    for (const divSection of divMain.getElementsByClassName('section')) {
        e.preventDefault();
        var delta = e.delta || e.wheelDelta;
        if (delta === undefined) {
          //we are on firefox
          delta = e.originalEvent.detail;
        }
        delta = Math.max(-1,Math.min(1,delta)) // cap the delta to [-1,1] for cross browser consistency
            offset = {x: divMain.scrollLeft, y: divMain.scrollTop};
     image_loc = {
        x: e.pageX + offset.x,
        y: e.pageY + offset.y
     }

     zoom_point = {x:image_loc.x/scale, y: image_loc.y/scale}

        // apply zoom
        scale += delta*factor * scale
        scale = Math.max(1,Math.min(max_scale,scale))

     zoom_point_new = {x:zoom_point.x * scale, y: zoom_point.y * scale}

     newScroll = {
        x: zoom_point_new.x - e.pageX,
        y: zoom_point_new.y - e.pageY
     }


      divSection.style.transform = `scale(${scale}, ${scale})`
      divMain.scrollTop = newScroll.y
      divMain.scrollLeft = newScroll.x
    }


  })
}

The updated fiddle is

https://jsfiddle.net/uy390v8t/1/

Results

Advertisement