Javascript Canvas : 在给定坐标处应用缩放

Javascript Canvas : Apply zoom at given coordinates

我正在努力实现 canvas 缩放(在鼠标滚轮上),使用 Vanilla Javascript,但没有成功。

滚动滚轮时,必须在鼠标坐标上应用缩放。 之前有人在这里问过,但是我的情况很不一样,考虑到 我不能使用 canvasContext.translateoffsetXoffsetY 的值必须保留在它的绝对表示(未缩放)

我真的很感激一些光。

在下面的代码片段中,我提供了我当前的和功能失调的实现。当您放大和缩小保持相同的缩放坐标时,您将看到它是如何工作的,但是一旦您将鼠标移动到新坐标(当 scale != 1 时)并继续缩放,新的偏移量就会变得错误。

// initiate variabks : canvas ref, offsets, scale...
const context   = document.getElementById('c').getContext('2d');
let scale       = 1;
let scaleFactor = 0.2;
let offsetX=0;
let offsetY=0;

// Handle mousenwheel zoom
context.canvas.onwheel= function(e){
  e.preventDefault();
  // calculate scale direction 6 new value
  let direction = e.deltaY > 0 ? 1 : -1;
  scale += scaleFactor * direction;
  // calculatethe new offsets (unscaled values)
  offsetX = e.offsetX - (e.offsetX  / scale);
  offsetY = e.offsetY - (e.offsetY / scale);  
  // apply new scale
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.scale(scale, scale);
}

// clear canvas and draw two boxes
// NO CHANGES CAN BE DONE IN THIS FUNCTION
function render(){
  context.beginPath();
  context.clearRect(0,0,context.canvas.width/scale,context.canvas.height/scale);
  context.rect(100-offsetX,50-offsetY,50,50);
  context.rect(200-offsetX,50-offsetY,50,50);
  context.stroke();
  
  document.getElementById("info").innerHTML=`
    Scale : ${scale} <br>
    Offets : ${ Math.round(offsetX) + ' , ' + Math.round(offsetY)} <br>
  `;
  requestAnimationFrame( render );
}
render()
<canvas id="c" width="350" height="150" style="border: 1px solid red;"></canvas>
<div id="info"></div>

主要思想是:

  context.translate(cx, cy);
  context.scale(scale, scale);
  context.translate(-cx, -cy);

其中 cx 和 cy 是对象组中心的坐标。

观察:负比例翻转对象。这就是我使用填充红色和黑色的原因。

// global vars : canvas, scroll & scale
let info        = document.getElementById("info");
const canvas    = document.getElementById('c');
let cw = canvas.width = 350,cx = cw/2;
let ch = canvas.height = 150,cy = 75;
const context   = c.getContext('2d');
let scrollX     = 0;
let scrollY     = 0;
let scale       = 1;
let scaleFactor = 0.02;


// clear canvas and draw two boxes
function render(){
  //context.clearRect(0, 0, canvas.width/scale, canvas.height/scale);
 
  
  context.beginPath();
  context.rect(100,50,50,50);
  context.fillStyle = "red";
  context.fill();
  
  context.beginPath();
  context.rect(200,50,50,50);
  context.fillStyle = "black";
  context.fill();
  
  info.innerHTML=`Scroll: ${scrollX},${scrollY} - Scale : ${scale}`
  requestAnimationFrame( render );
}


// handlencursor keys to move scroll
window.onkeydown = function(event){
  event.preventDefault();
  if(event.keyCode == 37)      scrollX -=10;
  else if(event.keyCode == 39) scrollX +=10;
  else if(event.keyCode == 38) scrollY -=10;
  else if(event.keyCode == 40) scrollY +=10;
};

// Handl mousenwheel zoom
canvas.onwheel= function(e){
  e.preventDefault();
  context.clearRect(-cw, -ch, 2 * cw, 2 * ch)
  

  let direction = e.deltaY > 0 ? 1 : -1;
  
  
  scrollX += Math.round(e.offsetX * scaleFactor * direction);
  scrollY += Math.round(e.offsetY * scaleFactor * direction);
  scale += scaleFactor * direction;
  
  context.setTransform(1, 0, 0, 1, 0, 0);
  
  context.translate(cx, cy);
  context.scale(scale, scale);
  context.translate(-cx, -cy);
  //render()
}

render()
<canvas id="c" width="350" height="150" style="border: 1px solid red;"></canvas>
<div id="info"></div>

我终于明白了... canvas不使用context.translate()

的任意坐标缩放

我附上我的解决方案,还有一点好处:canvas 平移(光标键滚动)。 我希望对某人有用。

// initiate variables : canvas ref, offsets, scale...
const context   = document.getElementById('c').getContext('2d');
let scale       = 1;
let scaleFactor = 0.2;
let scrollX     = 0;
let scrollY     = 0;

let info        = document.getElementById("info");


// Handle mousenwheel zoom
context.canvas.onwheel =  function(e){
  e.preventDefault();
  let previousScale= scale;
  
  // calculate scale direction 6 new scale value
  let direction = e.deltaY > 0 ? 1 : -1;
  scale += scaleFactor * direction;

  // calculate the new scroll values
  scrollX += ( e.offsetX / previousScale )  - (e.offsetX  / scale);
  scrollY += ( e.offsetY / previousScale ) - ( e.offsetY / scale);
  
  // apply new scale in a non acumulative way
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.scale(scale, scale);
}


// clear canvas and draw two boxes
function render(){
  context.beginPath();
  context.clearRect(0,0,context.canvas.width/scale, context.canvas.height/scale);
  context.rect(100-scrollX,50-scrollY,50,50);
  context.rect(200-scrollX,50-scrollY,50,50);
  context.stroke();
  
  info.innerHTML=`
    Scale : ${scale} <br>
    Scroll: ${scrollX},${scrollY} <br>
  `
  requestAnimationFrame( render );
}

// handlencursor keys to move scroll
window.onkeydown = function(event){
    event.preventDefault();
    if(event.keyCode == 37)      scrollX -=10;
    else if(event.keyCode == 39) scrollX +=10;
    else if(event.keyCode == 38) scrollY -=10;
    else if(event.keyCode == 40) scrollY +=10;
};

context.canvas.focus()
render()
<canvas id="c" width="400" height="150" style="border: 1px solid red;" tabindex="1"></canvas>
<div id="info"></div>