Set canvas zoom/scale origin

前端 未结 1 1311
予麋鹿
予麋鹿 2021-02-15 12:45

I\'m trying to create zoom effect on canvas, and I\'ve managed to do that, but there\'s a small problem. Zooming (scaling) origin is top left of the canvas. How can I specify a

相关标签:
1条回答
  • 2021-02-15 13:21

    If it is only zoom and pan then the solution is simple.

    You need to track two origins. One is the position of the mouse in the world coordinates (box and circle position) and the other is the position of the mouse in the screen coordinates (canvas pixels)

    You will need to get used to converting from one coordinate system to the other. That is done via the inverse function. From world coords to screen coords can be reversed with the inverse function that will convert from screen coords to world coords.

    Examples of inverting some simple functions

    • 2 * 10 = 20 the inverse is 20 / 10 = 2
    • 2 + 3 = 5 the inverse is 5 - 3 = 2
    • (3 - 1) * 5 = 10 the inverse is 10 * (1/5) + 1 = 3

    Multiply becomes * 1 over. ie x*5 becomes x * 1/5 (or just x/5) Adds become subtracts and subtract become add and what is first become last and last becomes first (3 - first) * last = result the inverse is result / last + first = 3

    So you zoom a coordinate (world coord position of box) and get the screen position of box in pixels. If you want the world coords of a screen pixel you apply the inverse

    It's all mouthful so here is your code doing what you need with some comments and I added mousemove, button stuff and other stuff because you need the mouse pos and no point in zooming if you can not pan, need to stop mouse button locking and stop wheel scrolling blah blah... To pan just move the world origin (in code) click drag in UI. Also I am lazy and got rid of the global.zoom.origin.x stuff now scale is well you know, and wx,wy,sx,sy are origins read code for what is what.

    var canvas    = document.getElementById("canvas");
    var context   = canvas.getContext("2d");
    canvas.width  = 600;
    canvas.height = 400;
    
    // lazy programmers globals
    var scale = 1;
    var wx    = 0; // world zoom origin
    var wy    = 0;
    var sx    = 0; // mouse screen pos
    var sy    = 0;
    
    var mouse = {};
    mouse.x   = 0; // pixel pos of mouse
    mouse.y   = 0;
    mouse.rx  = 0; // mouse real (world) pos
    mouse.ry  = 0;
    mouse.button = 0;
    
    function zoomed(number) { // just scale
      return Math.floor(number * scale);
    }
    // converts from world coord to screen pixel coord
    function zoomedX(number) { // scale & origin X
      return Math.floor((number - wx) * scale + sx);
    }
    
    function zoomedY(number) { // scale & origin Y
      return Math.floor((number - wy) * scale + sy);
    }
    
    // Inverse does the reverse of a calculation. Like (3 - 1) * 5 = 10   the inverse is 10 * (1/5) + 1 = 3
    // multiply become 1 over ie *5 becomes * 1/5  (or just /5)
    // Adds become subtracts and subtract become add.
    // and what is first become last and the other way round.
    
    // inverse function converts from screen pixel coord to world coord
    function zoomedX_INV(number) { // scale & origin INV
      return Math.floor((number - sx) * (1 / scale) + wx);
      // or return Math.floor((number - sx) / scale + wx);
    }
    
    function zoomedY_INV(number) { // scale & origin INV
      return Math.floor((number - sy) * (1 / scale) + wy);
      // or return Math.floor((number - sy) / scale + wy);
    }
    
    // draw everything in pixels coords
    function draw() {
      context.clearRect(0, 0, canvas.width, canvas.height);
      
      context.beginPath();
      context.rect(zoomedX(50), zoomedY(50), zoomed(100), zoomed(100));
      context.fillStyle = 'skyblue';
      context.fill();
    
      context.beginPath();
      context.arc(zoomedX(350), zoomedY(250), zoomed(50), 0, 2 * Math.PI, false);
      context.fillStyle = 'green';
      context.fill();
    }
    // wheel event must not be passive to allow default action to be prevented
    canvas.addEventListener("wheel", trackWheel, {passive:false}); 
    canvas.addEventListener("mousemove", move)
    canvas.addEventListener("mousedown", move)
    canvas.addEventListener("mouseup", move)
    canvas.addEventListener("mouseout", move) // to stop mouse button locking up 
    
    function move(event) { // mouse move event
      if (event.type === "mousedown") {
        mouse.button = 1;
      }
      else if (event.type === "mouseup" || event.type === "mouseout") {
        mouse.button = 0;
      }
    
      mouse.bounds = canvas.getBoundingClientRect();
      mouse.x = event.clientX - mouse.bounds.left;
      mouse.y = event.clientY - mouse.bounds.top;
      var xx  = mouse.rx; // get last real world pos of mouse
      var yy  = mouse.ry;
    
      mouse.rx = zoomedX_INV(mouse.x); // get the mouse real world pos via inverse scale and translate
      mouse.ry = zoomedY_INV(mouse.y);
      if (mouse.button === 1) { // is mouse button down 
        wx -= mouse.rx - xx; // move the world origin by the distance 
        // moved in world coords
        wy -= mouse.ry - yy;
        // recaculate mouse world 
        mouse.rx = zoomedX_INV(mouse.x);
        mouse.ry = zoomedY_INV(mouse.y);
      }
      draw();
    }
    
    function trackWheel(e) {
      
      if (e.deltaY < 0) {
        scale = Math.min(5, scale * 1.1); // zoom in
      } else {
        scale = Math.max(0.1, scale * (1 / 1.1)); // zoom out is inverse of zoom in
      }
      wx = mouse.rx; // set world origin
      wy = mouse.ry;
      sx = mouse.x; // set screen origin
      sy = mouse.y;
      mouse.rx = zoomedX_INV(mouse.x); // recalc mouse world (real) pos
      mouse.ry = zoomedY_INV(mouse.y);
      
      draw();
      e.preventDefault(); // stop the page scrolling
    }
    draw();
    body {
      background: gainsboro;
      margin: 0;
    }
    canvas {
      background: white;
      box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
    }
    <canvas id="canvas"></canvas>

    0 讨论(0)
提交回复
热议问题