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, ycoordinate where zoom happens - Calculate the new scaled
x, ycoordinate 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,yfrom 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
