在鼠标位置缩放/缩放

Nig*_*ige 3 html javascript canvas html5-canvas

我正在努力找出并确定如何根据此示例放大我的鼠标位置。(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();
});
Run Code Online (Sandbox Code Playgroud)

我想要的效果是基于这个例子(https://codepen.io/techslides/pen/zowLd?editors=0010)放大时。目前我上面的例子只缩放到“视口”的中心,但我希望它是我的光标当前所在的位置。

我已经在高低搜索了一个不是通过画布实现的解决方案。任何帮助,将不胜感激!

警告我使用滚轮事件的原因是为了模仿 Figma(设计工具)平移和缩放的交互。

Bli*_*n67 10

将画布用于可缩放的内容

缩放和平移元素非常成问题。它可以完成,但问题列表很长。我永远不会实现这样的接口。

考虑使用画布,通过 2D 或 WebGL 来显示这样的内容,以节省您自己的许多问题。

答案的第一部分是使用画布实现的。一样的界面view在平移和缩放元素的第二个示例中使用了。

一个简单的 2D 视图。

由于您只是平移和缩放,因此可以使用一种非常简单的方法。

下面的示例实现了一个名为 view 的对象。这保存当前的比例和位置(平移)

它为用户交互提供了两个功能。

  • 平移函数view.pan(amount)将按距离(以像素为单位)平移视图amount.xamount.y
  • 缩放功能view.scaleAt(at, amount)将缩放(在出变焦)由视图amount(表示比例改变的数),在所保存的位置at.xat.y以像素为单位。

在示例中,视图被应用到画布渲染上下文中,view.apply()并且每当视图发生变化时都会渲染一组随机框。平移和缩放是通过鼠标事件

使用画布 2D 上下文的示例

使用鼠标按钮拖动平移,滚轮缩放

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();
}
Run Code Online (Sandbox Code Playgroud)
body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

使用示例 element.style.transform

此示例使用元素样式变换属性来缩放和平移。

  • 请注意,我使用 2D 矩阵而不是 3d 矩阵,因为这会引入许多与下面使用的简单缩放和平移不兼容的问题。

  • 请注意,并非在所有情况下都将 CSS 转换应用于元素的左上角。在下面的示例中,原点位于元素的中心。因此,在缩放点时,必须通过减去元素大小的一半来调整缩放。元素大小不受变换的影响。

  • 注意边框、内边距和边距也会改变原点的位置。使用view.scaleAt(at, amount) at必须相对于元素的左上角最像素

  • 请注意,当您缩放和平移元素时,您需要考虑更多问题和注意事项,太多而无法放在一个答案中。这就是为什么这个答案从画布示例开始,因为它是迄今为止管理可缩放视觉内容的更安全的方法。

使用鼠标按钮拖动平移,滚轮缩放。如果您失去了位置(将页面放大或平移过远,请重新启动代码段)

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();
}
Run Code Online (Sandbox Code Playgroud)
body {
   user-select: none;    
   -moz-user-select: none;    
}
.zoomables {
    pointer-events: none;
    border: 1px solid black;
}
#zoomMe {
    position: absolute;
    top: 0px;
    left: 0px;
}
  
Run Code Online (Sandbox Code Playgroud)
<img id="zoomMe" class="zoomables" src="https://i.stack.imgur.com/C7qq2.png?s=328&g=1">
Run Code Online (Sandbox Code Playgroud)

  • 几点注意:事件名称是“wheel”,而不是“mousewheel”,这是仅由某些UA实现的旧版本。您可能想要处理平滑滚动 (`const scroll_ratio = (Math.abs(event.deltaY) / 100) + 1; if (event.deltaY &lt; 0) { view.scaleAt({x, y},scroll_ratio); ( ...) view.scaleAt({x, y}, 1 /scroll_ratio);`) (2认同)

Bus*_*men 9

第二个链接的放大有点极端,所以我尝试添加一些约束。您可以取消注释并玩更多。目前,外观和工作原理与恕我直言完全相同。

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 });
Run Code Online (Sandbox Code Playgroud)
.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%;
}
Run Code Online (Sandbox Code Playgroud)
<div class="container">
  <div class="image">
    <img src="https://picsum.photos/400/400" />
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)