最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Zoom inout at mouse position in canvas - Stack Overflow

programmeradmin1浏览0评论

I'm trying to implement zoom feature using p5.js. Current zoom level and x and y positions are stored in controls.view object. Default or (0, 0) position is at top left corner. Problem is to adjust x and y position values on zoom in/out so that no matter what the current position of the view is, it will stay at point of zoom or mouse cursor.

Scaling and translating is executed in draw function by x, y and zoom values so those values must be modified directly (can't run translate on canvas directly). Here is 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=".js/0.6.0/p5.min.js"></script>

I'm trying to implement zoom feature using p5.js. Current zoom level and x and y positions are stored in controls.view object. Default or (0, 0) position is at top left corner. Problem is to adjust x and y position values on zoom in/out so that no matter what the current position of the view is, it will stay at point of zoom or mouse cursor.

Scaling and translating is executed in draw function by x, y and zoom values so those values must be modified directly (can't run translate on canvas directly). Here is 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./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);
}

This are some of the similar questions that I tried to implement: Zoom Canvas to Mouse Cursor, Zoom in on a point (using scale and translate)

Share Improve this question asked Mar 12, 2018 at 22:07 Nenad VracarNenad Vracar 122k16 gold badges160 silver badges183 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 9 +200

In my mind, the way zoom works is that based on the position of the point of interest (in your case mouse), the zoom has a weighted impact on the (x, y) position of the canvas. If the mouse is at the top left, this weight should be (0,0), if it's at the bottom right, it should be (1,1). And the centre should be (0.5,0.5).

So, assuming we have the weights, whenever the zoom changes, the weights show us how far the top left corner of the canvas is from the point of interest, and we should move it away by the weight*dimension*(delta zoom).

Note that in order to calculate the weights, we need to consider the zoomed in/out value of the dimensions. So if width is 100 and zoom is 0.5, we should assume the width is 50. So all the values would be absolute (since the mouse (x,y) are absolute).

so, it's like:

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

    // pute 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;
}

You can try it in this codepen.

Or a bit simpler, pute the weights inline:

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;
发布评论

评论列表(0)

  1. 暂无评论