在 canvas 中的鼠标位置缩放 in/out

Zoom in/out at mouse position in canvas

我正在尝试使用 p5.js 实现缩放功能。当前缩放级别和 x 和 y 位置存储在 controls.view 对象中。默认或 (0, 0) 位置在左上角。问题是调整缩放时的 x 和 y 位置值 in/out 以便无论视图的当前位置是什么,它都会停留在缩放点或鼠标光标处。

缩放和平移是在 draw 函数中通过 x、y 和缩放值执行的,因此必须直接修改这些值(不能 运行 直接在 canvas 上平移)。这里是 Codepen.

let canvas, circles;
const controls = {
  view: {
    x: 0,
    y: 0,
    zoom: 1
  },
  viewPos: {
    prevX: null,
    prevY: null,
    isDragging: false
  },
}

function setup() {
  canvas = createCanvas(window.innerWidth, window.innerHeight);
  canvas.mouseWheel(e => Controls.zoom(controls).worldZoom(e))
  circles = Circle.create(100)
}

function draw() {
  background(100)
  translate(controls.view.x, controls.view.y);
  scale(controls.view.zoom)
  circles.forEach(circle => circle.show());
}

window.mousePressed = e => Controls.move(controls).mousePressed(e)
window.mouseDragged = e => Controls.move(controls).mouseDragged(e);
window.mouseReleased = e => Controls.move(controls).mouseReleased(e)


class Controls {
  static move(controls) {
    function mousePressed(e) {
      controls.viewPos.isDragging = true;
      controls.viewPos.prevX = e.clientX;
      controls.viewPos.prevY = e.clientY;
    }

    function mouseDragged(e) {
      const {
        prevX,
        prevY,
        isDragging
      } = controls.viewPos;
      if (!isDragging) return;

      const pos = {
        x: e.clientX,
        y: e.clientY
      };
      const dx = pos.x - prevX;
      const dy = pos.y - prevY;

      if (prevX || prevY) {
        controls.view.x += dx;
        controls.view.y += dy;
        controls.viewPos.prevX = pos.x, controls.viewPos.prevY = pos.y
      }
    }

    function mouseReleased(e) {
      controls.viewPos.isDragging = false;
      controls.viewPos.prevX = null;
      controls.viewPos.prevY = null;
    }

    return {
      mousePressed,
      mouseDragged,
      mouseReleased
    }
  }

  static zoom(controls) {
    // function calcPos(x, y, zoom) {
    //   const newX = width - (width * zoom - x);
    //   const newY = height - (height * zoom - y);
    //   return {x: newX, y: newY}
    // }

    function worldZoom(e) {
      const {
        x,
        y,
        deltaY
      } = e;
      const direction = deltaY > 0 ? -1 : 1;
      const factor = 0.1;
      const zoom = 1 * direction * factor;

      controls.view.zoom += zoom;

      controls.view.x += -(x * direction * factor);
      controls.view.y += -(y * direction * factor);
    }

    return {
      worldZoom
    }
  }
}


class Circle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  show() {
    fill(255);
    noStroke();
    ellipse(this.x, this.y, 15, 15);
  }

  static create(count) {
    return Array.from(Array(count), () => {
      const x = random(-500, width + 500);
      const y = random(-500, height + 500);
      return new this(x, y);
    })
  }
}
body {margin: 0; padding: 0;}
canvas {vertical-align: top;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.0/p5.min.js"></script>

function worldZoom(e) {
  const {x, y, deltaY} = e;
  const direction = deltaY > 0 ? -1 : 1;
  const factor = 0.1;
  const zoom = 1 * direction * factor;

  controls.view.zoom += zoom;

  controls.view.x += -(x * direction * factor);
  controls.view.y += -(y * direction * factor);
}

这是我尝试解决的一些类似问题: Zoom Canvas to Mouse Cursor, Zoom in on a point (using scale and translate)

在我看来,缩放的工作方式是根据兴趣点的位置(在您的例子中是鼠标),缩放对 [=35] 的 (x, y) 位置有加权影响=].如果鼠标在左上角,这个权重应该是(0,0),如果在右下角,它应该是(1,1)。中心应该是 (0.5,0.5).

因此,假设我们有权重,每当缩放发生变化时,权重会向我们显示 canvas 的左上角与兴趣点的距离,我们应该将其移开weight*dimension*(delta zoom).

请注意,为了计算权重,我们需要考虑尺寸的缩放 in/out 值。因此,如果宽度为 100 且缩放为 0.5,我们应该假设宽度为 50。所以所有的值都是绝对的(因为鼠标 (x,y) 是绝对的)。

所以,就像:

function worldZoom(e) {
    const {x, y, deltaY} = e;
    const direction = deltaY > 0 ? -1 : 1;
    const factor = 0.01;
    const zoom = 1 * direction * factor;

    // compute the weights for x and y
    const wx = (x-controls.view.x)/(width*controls.view.zoom);
    const wy = (y-controls.view.y)/(height*controls.view.zoom);

    // apply the change in x,y and zoom.
    controls.view.x -= wx*width*zoom;
    controls.view.y -= wy*height*zoom;
    controls.view.zoom += zoom;
}

您可以在this codepen中尝试。

或者更简单一点,内联计算权重:

controls.view.x -= (x-controls.view.x)/(controls.view.zoom)*zoom;
controls.view.y -= (y-controls.view.y)/(controls.view.zoom)*zoom;
controls.view.zoom += zoom;