admin管理员组文章数量:1330590
Almost every Graphic Editor allows to zoom the image and a "working area background" with a Ctrl + mouse wheel.
The tricky part is that there are many UI elements involved:
- "working area background" - that darker area behind the image that is zoomed
- scrollbars which are updated correctly and can be used to pan around the working area
- actual image (that blue blueprint pattern) - element that visually get's zoomed
Notice that the zooming behavior is different depending on whether the mouse pointer was above the image or not:
- if it's above the image then image get's zoomed and moved
- if it's above the "working area background" then the image only get's zoomed and keeps it's position
- note: scrollbars are updated in both cases!
It seems to be nicely implemented decades ago.
Are there any open-source projects with a similar "zoom + move + scrollbars" behavior to look at their code and learn from it?
Thanks!
Almost every Graphic Editor allows to zoom the image and a "working area background" with a Ctrl + mouse wheel.
The tricky part is that there are many UI elements involved:
- "working area background" - that darker area behind the image that is zoomed
- scrollbars which are updated correctly and can be used to pan around the working area
- actual image (that blue blueprint pattern) - element that visually get's zoomed
Notice that the zooming behavior is different depending on whether the mouse pointer was above the image or not:
- if it's above the image then image get's zoomed and moved
- if it's above the "working area background" then the image only get's zoomed and keeps it's position
- note: scrollbars are updated in both cases!
It seems to be nicely implemented decades ago.
Are there any open-source projects with a similar "zoom + move + scrollbars" behavior to look at their code and learn from it?
Thanks!
Share Improve this question asked Jul 18, 2020 at 13:40 mykola.rykovmykola.rykov 5728 silver badges13 bronze badges2 Answers
Reset to default 5I did recently something exactly like i.e: Photoshop.
Heads up: The task was tricky. Here are my discoveries and implementation suggestions.
GitHub: ZoomPan.js
Custom scrollbars
Don't use the browser's default scrollbars on your Viewport element. I really tried to be lazy and create a wrapping, bigger #area
Element that will be used to force native scrollbars on the #viewport
. Too many messy calculations and scrollTo()
adjustments.
I ended up opting for my own custom scrollbars. Regarding the missing #area
- I just decided to calculate that "area" size width
and height
on the fly (recalculated on init and zoom), used only to determine the scrollbar-thumbs sizes and to prevent the panned canvas go beyond some defined "safe" padd edge. Another pros is that I can position and size them however it fits the app with other neighboring UI elements.
HTML and CSS
Here's the markup
<div id="editor">
<div id="viewport"><div id="canvas"></div></div>
<div class="scrollTrack" id="scrollTrack-x"><div class="scrollThumb"></div></div>
<div class="scrollTrack" id="scrollTrack-y"><div class="scrollThumb"></div></div>
</div>
the important part is the absolute centering of #canvas
within the #viewport
. Easily done with CSS and flex:
#viewport {
position: relative;
overflow: hidden; /* We will make or own scrollbars */
width: 100%; /* Fit into #editor */
height: 100%;
display: flex; /* Center the #canvas */
align-items: center; /* Center the #canvas */
justify-content: center; /* Center the #canvas */
}
#canvas {
flex: none; /* Prevent flexing */
transform-origin: 50% 50%;
/* width and height here or rather via your app settings */
}
The logic
The offsetting and scaling logic is all calculated from the #canvas's center coordinates. Also notice the CSS: transform-origin: 50% 50%;
.
const offset = {x:0, y:0}; // Canvas offset (0,0 from center)
Area
There's an important aspect of the editor, and that's the aforementioned fictive pannable area. Say you want to pan the Canvas inside the Viewport, you want to prevent the canvas to exit pletely the viewport on any side. You need to restrict the pan motion to a specified padd
amount of canvas min visibility pixels.
You need to recalculate that fictive area size after every scale (zoom) operation:
const bcrVpt = elVpt.getBoundingClientRect();
const bcrCvs = elCvs.getBoundingClientRect();
// Fictive "outer bounding area" size:
areaWidth = (bcrVpt.width - padd) * 2 + bcrCvs.width;
areaHeight = (bcrVpt.height - padd) * 2 + bcrCvs.height;
Before applying translate and scale to your #canvas you can always make sure to fix the canvas's offset.x,y
to not exceed the available pan space given by areaWidth
and areaHeight
.
Panning
Panning is the simplest. it's calculated like:
offset.x += evt.movementX;
offset.y += evt.movementY;
where evt.movement[XY]
is the difference in the pointer (mouse) start / current positions, or if you will:
offset.x += pointerStartX - pointerCurrentX;
offset.y += pointerStartY - pointerCurrentY;
and you can apply immediately the transformations to the #canvas
element. More on that later.
Scaling
Scaling is nothing but changing the scale by a scale factor by a given delta
(-1
or +1
)
// On wheel or +/- buttons set delta to +1 or -1
const changeScale = (delta) => {
scale *= Math.exp(delta * scaleFactor);
}
Transform by scale
Scaling was easy, now we have to change the #canvas offset
translation depending on the pointer position inside the #viewport by the change in scale to allow the user to wheel-zoom on an exact point.
To calculate scale-transforms you first need to normalize the mouse position relative to the #canvas center in its current sizes state (which might be: original, down-scaled or up-scaled).
Let's say, for the X axis: how much px to offset the #canvas?
const bcrCvs = elCvs.getBoundingClientRect();
// Get XY coords of #canvas FROM CENTER!
// This values are "current" (on the currently transformed #canvas)
const x = evt.x - bcrCvs.left - bcrCvs.width / 2;
// Remember the current scale
const scaleOld = scale;
// Change the scale value by delta
changeScale(delta); // PS: scale is now changed!
// Calculate the XY as if the element is in its original, non-scaled size:
const xOrg = xReal / scaleOld;
// Calculate the scaled XY
const xNew = xOrg * scale; // PS: scale here is the new scale.
// Retrieve the XY difference to be used as the change in offset.
const xDiff = xReal - xNew;
offset.x += xDiff;
add also for Y axis.
Scrollbars
Zooming and panning should work by now. One left thing to do is: scrollbars.
Thanks to the changing fictive area width, height values, we can now determine the scrollbars size. For the X axis scrollbar:
const bcrVpt = elVpt.getBoundingClientRect();
const bcrCvs = elCvs.getBoundingClientRect();
// Fictive "outer bounding area" size:
areaWidth = (bcrVpt.width - padd) * 2 + bcrCvs.width;
const thumbSizeX = bcrVpt.width ** 2 / areaWidth;
const cvsRelX = bcrCvs.left - bcrVpt.left;
const thumbPosX = (bcrVpt.width - cvsRelX - padd) / bcrVpt.width * thumbSizeX;
elScrXThumb.style.width = `${thumbSizeX}px`;
elScrXThumb.style.left = `${thumbPosX}px`;
Drag scrollbars
one thing left to do is the scrollbars dragging. Simply change the #canvas offset by:
offset.x -= (areaWidth / elVpt.offsetWidth) * evt.movementX;
Apply the above for the Y scrollbar as well.
That was pretty much it.
Some more missing improvements are functions like scaleToFit(), to scale on init the #canvas to best fit the #viewport and the mouse controls.
Regarding the keyboard + mouse, use the JS's Event.ctrlKey || Event.metaKey
to register for if such keys were pressed during i.e: the "wheel" event. etc.
Find more in the provided github example.
Some other related resources:
- Calculation of a mouse position after multiple scales on different coordinates of image
- Panning Image When Overflow: scroll
The trigger is called wheel evenent
, you can read about it here.
Don't confuse the wheel event with the scroll event. The default action of a wheel event is implementation-specific, and doesn't necessarily dispatch a scroll event
An implementation of what you show, would start taking the current position of the mouse position, and use it to enlarge/reduce the container size consequentially. The effect that when you zoom on a particular part of the image is kept "centered" in the screen, is made by continually reposition the image based on the actual scale.
The scrollbars are reacting to the dimensions change 'cause they have a fixed width and height, you can see it because zooming in or out does not change the "editor" dimensions.
本文标签:
版权声明:本文标题:javascript - How to implement zooming with a mouse wheel like it's done in a typical graphic editor - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742254209a2441327.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论