简体   繁体   English

在鼠标位置缩放/缩放

[英]Zoom/scale at mouse position

I am struggling to figure out and determine how to zoom on my mouse position based on this example.我正在努力找出并确定如何根据这个例子放大我的鼠标位置。 ( https://stackblitz.com/edit/js-fxnmkm?file=index.js ) https://stackblitz.com/edit/js-fxnmkm?file=index.js

let node,
    scale = 1,
    posX = 0,
    posY = 0,
    node = document.querySelector('.frame');

const render = () => {
  window.requestAnimationFrame(() => {
    let val = `translate3D(${posX}px, ${posY}px, 0px) scale(${scale})`
    node.style.transform = val
  })
}

window.addEventListener('wheel', (e) => {
  e.preventDefault();

  // Zooming happens here
  if (e.ctrlKey) {
    scale -= e.deltaY * 0.01;
  } else {
    posX -= e.deltaX * 2;
    posY -= e.deltaY * 2;
  }

  render();
});

My desired effect is based on this example ( https://codepen.io/techslides/pen/zowLd?editors=0010 ) when zooming in. Currently my example above only scales to the center of the "viewport" but I want it to be where my cursor currently is.放大时,我想要的效果基于此示例( https://codepen.io/techslides/pen/zowLd?editors=0010 )。目前我上面的示例仅缩放到“视口”的中心,但我希望它是我的光标当前所在的位置。

I have searched high and low for a solution that is not implemented via canvas.我已经搜索了高低不通过画布实现的解决方案。 Any help would be appreciated!任何帮助,将不胜感激!

Caveat The reason why I am using the wheel event is to mimic the interaction of Figma (the design tool) panning and zooming.警告我使用轮子事件的原因是为了模仿 Figma(设计工具)平移和缩放的交互。

Use the canvas for zoomable content将画布用于可缩放内容

Zooming and panning elements is very problematic.缩放和平移元素是非常有问题的。 It can be done but the list of issues is very long.可以做到,但问题清单很长。 I would never implement such an interface.我永远不会实现这样的接口。

Consider using the canvas, via 2D or WebGL to display such content to save your self many many problems.考虑使用画布,通过 2D 或 WebGL 来显示此类内容,以节省您自己的许多问题。

The first part of the answer is implemented using the canvas.答案的第一部分是使用画布实现的。 The same interface view is used in the second example that pans and zooms an element.在第二个示例中使用相同的界面view平移和缩放元素。

A simple 2D view.一个简单的二维视图。

As you are only panning and zooming then a very simple method can be used.由于您只是平移和缩放,因此可以使用一种非常简单的方法。

The example below implements an object called view.下面的示例实现了一个名为 view 的对象。 This holds the current scale and position (pan)这保存了当前的比例和位置(平移)

It provides two function for user interaction.它为用户交互提供了两个功能。

  • Panning the function view.pan(amount) will pan the view by distance in pixels held by amount.x , amount.y平移函数view.pan(amount)将平移视图的距离(以数量为单位的像素), amount.x amount.y
  • Zooming the function view.scaleAt(at, amount) will scale (zoom in out) the view by amount (a number representing change in scale), at the position held by at.x , at.y in pixels.缩放函数view.scaleAt(at, amount)将在at.xat.y以像素为单位的位置按amount (表示比例变化的数字)缩放(缩小)视图。

In the example the view is applied to the canvas rendering context using view.apply() and a set of random boxes are rendered whenever the view changes.在示例中,使用view.apply()将视图应用于画布渲染上下文,并且每当视图更改时都会渲染一组随机框。 The panning and zooming is via mouse events平移和缩放是通过鼠标事件

Example using canvas 2D context使用画布 2D 上下文的示例

Use mouse button drag to pan, wheel to zoom使用鼠标按钮拖动平移,滚轮缩放

 const ctx = canvas.getContext("2d"); canvas.width = 500; canvas.height = 500; const rand = (m = 255, M = m + (m = 0)) => (Math.random() * (M - m) + m) | 0; const objects = []; for (let i = 0; i < 100; i++) { objects.push({x: rand(canvas.width), y: rand(canvas.height),w: rand(40),h: rand(40), col: `rgb(${rand()},${rand()},${rand()})`}); } requestAnimationFrame(drawCanvas); const view = (() => { const matrix = [1, 0, 0, 1, 0, 0]; // current view transform var m = matrix; // alias var scale = 1; // current scale var ctx; // reference to the 2D context const pos = { x: 0, y: 0 }; // current position of origin var dirty = true; const API = { set context(_ctx) { ctx = _ctx; dirty = true }, apply() { if (dirty) { this.update() } ctx.setTransform(m[0], m[1], m[2], m[3], m[4], m[5]) }, get scale() { return scale }, get position() { return pos }, isDirty() { return dirty }, update() { dirty = false; m[3] = m[0] = scale; m[2] = m[1] = 0; m[4] = pos.x; m[5] = pos.y; }, pan(amount) { if (dirty) { this.update() } pos.x += amount.x; pos.y += amount.y; dirty = true; }, scaleAt(at, amount) { // at in screen coords if (dirty) { this.update() } scale *= amount; pos.x = at.x - (at.x - pos.x) * amount; pos.y = at.y - (at.y - pos.y) * amount; dirty = true; }, }; return API; })(); view.context = ctx; function drawCanvas() { if (view.isDirty()) { ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.clearRect(0, 0, canvas.width, canvas.height); view.apply(); // set the 2D context transform to the view for (i = 0; i < objects.length; i++) { var obj = objects[i]; ctx.fillStyle = obj.col; ctx.fillRect(obj.x, obj.y, obj.h, obj.h); } } requestAnimationFrame(drawCanvas); } canvas.addEventListener("mousemove", mouseEvent, {passive: true}); canvas.addEventListener("mousedown", mouseEvent, {passive: true}); canvas.addEventListener("mouseup", mouseEvent, {passive: true}); canvas.addEventListener("mouseout", mouseEvent, {passive: true}); canvas.addEventListener("wheel", mouseWheelEvent, {passive: false}); const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false}; function mouseEvent(event) { if (event.type === "mousedown") { mouse.button = true } if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false } mouse.oldX = mouse.x; mouse.oldY = mouse.y; mouse.x = event.offsetX; mouse.y = event.offsetY if(mouse.button) { // pan view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY}); } } function mouseWheelEvent(event) { var x = event.offsetX; var y = event.offsetY; if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1) } else { view.scaleAt({x, y}, 1 / 1.1) } event.preventDefault(); }
 body { background: gainsboro; margin: 0; } canvas { background: white; box-shadow: 1px 1px 1px rgba(0, 0, 0, .2); }
 <canvas id="canvas"></canvas>

Example using element.style.transform使用element.style.transform的示例

This example uses the element style transform property to zoom and pan.此示例使用元素样式转换属性进行缩放和平移。

  • Note that I use a 2D matrix rather than the 3d matrix as that can introduce many problems not compatible with the simple zoom and pan used below.请注意,我使用 2D 矩阵而不是 3d 矩阵,因为这会引入许多与下面使用的简单缩放和平移不兼容的问题。

  • Note that CSS transforms are not applied to the top left of the element in all cases.请注意,并非在所有情况下都将 CSS 转换应用于元素的左上角。 In the example below the origin is in the center of the element.在下面的示例中,原点位于元素的中心。 Thus when zooming the zoom at point must be adjusted by subtracting half the elements size.因此,当缩放时,必须通过减去元素大小的一半来调整的缩放。 The element size is not effected by the transform.元素大小不受变换的影响。

  • Note borders, padding, and margins will also change the location of the origin.注意边框、填充和边距也会改变原点的位置。 To work with view.scaleAt(at, amount) at must be relative to the top left most pixel of the element要使用view.scaleAt(at, amount) at必须相对于元素的左上角像素

  • Note there are many more problems and caveats you need to consider when you zoom and pan elements, too many to fit in a single answer.请注意,当您缩放和平移元素时,您需要考虑更多问题和注意事项,这些问题和注意事项太多,无法放在一个答案中。 That is why this answer starts with a canvas example as it is by far the safer method to managing zoom-able visual content.这就是为什么这个答案以画布示例开始的原因,因为它是迄今为止管理可缩放视觉内容的更安全的方法。

Use mouse button drag to pan, wheel to zoom.使用鼠标按钮拖动平移,滚轮缩放。 If you lose your position (zoom too far in out or panned of the page restart the snippet)如果您失去位置(放大或平移页面太远,请重新启动代码段)

 const view = (() => { const matrix = [1, 0, 0, 1, 0, 0]; // current view transform var m = matrix; // alias var scale = 1; // current scale const pos = { x: 0, y: 0 }; // current position of origin var dirty = true; const API = { applyTo(el) { if (dirty) { this.update() } el.style.transform = `matrix(${m[0]},${m[1]},${m[2]},${m[3]},${m[4]},${m[5]})`; }, update() { dirty = false; m[3] = m[0] = scale; m[2] = m[1] = 0; m[4] = pos.x; m[5] = pos.y; }, pan(amount) { if (dirty) { this.update() } pos.x += amount.x; pos.y += amount.y; dirty = true; }, scaleAt(at, amount) { // at in screen coords if (dirty) { this.update() } scale *= amount; pos.x = at.x - (at.x - pos.x) * amount; pos.y = at.y - (at.y - pos.y) * amount; dirty = true; }, }; return API; })(); document.addEventListener("mousemove", mouseEvent, {passive: false}); document.addEventListener("mousedown", mouseEvent, {passive: false}); document.addEventListener("mouseup", mouseEvent, {passive: false}); document.addEventListener("mouseout", mouseEvent, {passive: false}); document.addEventListener("wheel", mouseWheelEvent, {passive: false}); const mouse = {x: 0, y: 0, oldX: 0, oldY: 0, button: false}; function mouseEvent(event) { if (event.type === "mousedown") { mouse.button = true } if (event.type === "mouseup" || event.type === "mouseout") { mouse.button = false } mouse.oldX = mouse.x; mouse.oldY = mouse.y; mouse.x = event.pageX; mouse.y = event.pageY; if(mouse.button) { // pan view.pan({x: mouse.x - mouse.oldX, y: mouse.y - mouse.oldY}); view.applyTo(zoomMe); } event.preventDefault(); } function mouseWheelEvent(event) { const x = event.pageX - (zoomMe.width / 2); const y = event.pageY - (zoomMe.height / 2); if (event.deltaY < 0) { view.scaleAt({x, y}, 1.1); view.applyTo(zoomMe); } else { view.scaleAt({x, y}, 1 / 1.1); view.applyTo(zoomMe); } event.preventDefault(); }
 body { user-select: none; -moz-user-select: none; } .zoomables { pointer-events: none; border: 1px solid black; } #zoomMe { position: absolute; top: 0px; left: 0px; }
 <img id="zoomMe" class="zoomables" src="https://i.stack.imgur.com/C7qq2.png?s=328&g=1">

This zoom in the 2nd link is a bit extreme so I tried to add some constraints.第二个链接的放大有点极端,所以我尝试添加一些约束。 You can uncomment them and play more.您可以取消注释它们并玩更多。 For now looks and works exactly the same IMHO.现在看起来和工作完全一样恕我直言。

 const container = document.querySelector('.container'); const image = document.querySelector('.image'); const speed = 0.5; let size = { w: image.offsetWidth, h: image.offsetHeight }; let pos = { x: 0, y: 0 }; let target = { x: 0, y: 0 }; let pointer = { x: 0, y: 0 }; let scale = 1; window.addEventListener('wheel', event => { event.preventDefault(); pointer.x = event.pageX - container.offsetLeft; pointer.y = event.pageY - container.offsetTop; target.x = (pointer.x - pos.x) / scale; target.y = (pointer.y - pos.y) / scale; scale += -1 * Math.max(-1, Math.min(1, event.deltaY)) * speed * scale; // Uncomment to constrain scale // const max_scale = 4; // const min_scale = 1; // scale = Math.max(min_scale, Math.min(max_scale, scale)); pos.x = -target.x * scale + pointer.x; pos.y = -target.y * scale + pointer.y; // Uncomment for keeping the image within area (works with min scale = 1) // if (pos.x > 0) pos.x = 0; // if (pos.x + size.w * scale < size.w) pos.x = -size.w * (scale - 1); // if (pos.y > 0) pos.y = 0; // if (pos.y + size.h * scale < size.h) pos.y = -size.h * (scale - 1); image.style.transform = `translate(${pos.x}px,${pos.y}px) scale(${scale},${scale})`; }, { passive: false });
 .container { width: 400px; height: 400px; overflow: hidden; outline: 1px solid gray; } .image { width: 100%; height: 100%; transition: transform .3s; transform-origin: 0 0; } img { width: auto; height: auto; max-width: 100%; }
 <div class="container"> <div class="image"> <img src="https://picsum.photos/400/400" /> </div> </div>

Here's my version, support pan and zoom (hold CTRL key).这是我的版本,支持平移和缩放(按住 CTRL 键)。

 let editor = document.getElementById("editor"); let editorCanvas = editor.querySelector(".canvas"); let scale = 1.0; const minScale = 0.1; const maxScale = 8; const scaleStep = 0.003; let ctrlDown = false; let dragging = false; let dragStartX = 0; let dragStartY = 0; let previousScrollLeft = 0; let previousScrollTop = 0; window.addEventListener("keydown", (e) => { if (e.ctrlKey) { ctrlDown = true; editorCanvas.style.cursor = "move"; } }); window.addEventListener("keyup", (e) => { ctrlDown = false; editorCanvas.style.cursor = "default"; }); editor.addEventListener("mousedown", (e) => { dragging = true; dragStartX = ex - editor.offsetLeft; dragStartY = ey - editor.offsetTop; previousScrollLeft = editor.scrollLeft; previousScrollTop = editor.scrollTop; }); editor.addEventListener("mouseup", (e) => { dragging = false; }); editor.addEventListener("mousemove", (e) => { if (ctrlDown && dragging) { requestAnimationFrame(() => { let currentX = ex - editor.offsetLeft; let currentY = ey - editor.offsetTop; let scrollX = previousScrollLeft + (dragStartX - currentX) let scrollY = previousScrollTop + (dragStartY - currentY); editor.scroll(scrollX, scrollY); }); } }); editor.addEventListener("wheel", (e) => { e.preventDefault(); requestAnimationFrame(() => { if (e.ctrlKey) { scale -= e.deltaY * scaleStep; if (scale < minScale) { scale = minScale; } if (scale > maxScale) { scale = maxScale; } if (scale < 1) { editorCanvas.style.transformOrigin = "50% 50% 0"; } else { editorCanvas.style.transformOrigin = "0 0 0"; } editorCanvas.style.transform = `matrix(${scale}, 0, 0, ${scale}, 0, 0)`; let rect = editorCanvas.getBoundingClientRect(); let ew = rect.width; let eh = rect.height; let mx = ex - editor.offsetLeft; let my = ey - editor.offsetTop; editor.scroll((ew - editor.offsetWidth) * (mx / editor.clientWidth), (eh - editor.offsetHeight) * (my / editor.clientHeight)); } else { editor.scrollTop += e.deltaY; editor.scrollLeft += e.deltaX; } }); }, { passive: false });
 body { background-color: lightgray; } #editor { position: relative; width: 1024px; height: 768px; box-sizing: border-box; border: 1px solid darkgray; background-color: gray; overflow: auto; } .canvas { position: relative; width: 100%; height: 100%; background-color: white; } .frame { position: absolute; box-sizing: border-box; border: 1px solid darkslategrey; transition: all 0.25s; } .frame.one { top: 80px; left: 400px; width: 300px; height: 250px; background-color: pink; } .frame.two { top: 350px; left: 150px; width: 200px; height: 150px; background-color: gold; } .frame.three { top: 130px; left: 70px; width: 100px; height: 150px; background-color: cyan; } .frame.four { top: 368px; left: 496px; width: 32px; height: 32px; background-color: lime; } .frame:hover { cursor: pointer; border: 3px solid darkslategrey; } .frame:active { filter: invert(); }
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Zoom Editor</title> </head> <body> <div id="editor"> <div class="canvas"> <div class="frame one"></div> <div class="frame two"></div> <div class="frame three"></div> <div class="frame four"></div> </div> </div> </body> </html>

I think you need to use an external jquery plugin to achieve this:我认为您需要使用外部 jquery 插件来实现此目的:

js file : https://ariutta.github.io/svg-pan-zoom/dist/svg-pan-zoom.js js文件: https ://ariutta.github.io/svg-pan-zoom/dist/svg-pan-zoom.js

working demo : https://ariutta.github.io/svg-pan-zoom/工作演示: https ://ariutta.github.io/svg-pan-zoom/

for more clarification please visit: https://github.com/ariutta/svg-pan-zoom如需更多说明,请访问: https ://github.com/ariutta/svg-pan-zoom

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM