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:
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 newscaled 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