设置画布缩放/缩放原点 [英] Set canvas zoom/scale origin

查看:103
本文介绍了设置画布缩放/缩放原点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在画布上创建缩放效果,并且设法做到了,但是有一个小问题。缩放(缩放)原点位于画布的左上方。如何指定缩放/比例原点?



我想我需要使用 translate ,但我不需要知道我应该在哪里实现它。



我想用作缩放原点的是鼠标位置,但是为了简单起见,画布的中心就可以了。

>

JSFiddle



  var canvas = document.getElementById( canvas); var context = canvas.getContext ( 2d); canvas.width = 600; canvas.height = 400; var global = {zoom:{origin:{x:null,y:null,},scale:1,},}; function zoomed(number ){return Math.floor(number * global.zoom.scale);} function draw(){context.beginPath(); context.rect(zoomed(50),zoomed(50),zoomed(100),zoomed(100)); context.fillStyle =天蓝色; context.fill(); context.beginPath(); context.arc(zoomed(350),zoomed(250),zoomed(50),0,2 * Math.PI,false); context.fillStyle =绿色; context.fill();} draw(); canvas.addEventListener( wheel,trackWheel); canvas.addEventListener( wheel,zoom); function zoom(){context.setTransform(1,0,0,1, 0,0); context.clearRect(0,0,canvas.width,canvas.height); draw();} function trackWheel(e){if(e.deltaY <0){if(global.zoom.scale< 5){global.zoom.scale * = 1.1; }} else {if(global.zoom.scale> 0.1){global.zoom.scale * = 0.9; }} global.zoom.scale = parseFloat(global.zoom.scale.toFixed(2));}  

  body {背景:gainsboro;边距:0;}画布{背景:白色; box-shadow:1px 1px 1px rgba(0,0,0,.2);}  

 < canvas id = canvas>< / canvas>  





更新1



似乎没有其他相关问题这个主题是关于SO的,但是我不能直接在我的代码中实现。



我试图检查演示将画布缩放为鼠标光标中提供的Phrogz,但它太复杂了(至少对我来说)。试图实施他的解决方案:

  ctx.translate(pt.x,pt.y); 
ctx.scale(factor,factor);
ctx.translate(-pt.x,-pt.y);

JSFiddle



  var canvas = document.getElementById( canvas); var context = canvas.getContext( 2d); canvas.width = 600; canvas.height = 400; var global = {zoom:{origin:{x:null ,y:null,},scale:1,},}; function draw(){context.beginPath(); context.rect(50,50,100,100); context.fillStyle =天蓝色; context.fill(); context.beginPath(); context.arc(350,250,50,0,2 * Math.PI,false); context.fillStyle =绿色; context.fill();} draw(); canvas.addEventListener( wheel,trackWheel); canvas.addEventListener( wheel,trackMouse); canvas.addEventListener( wheel,zoom); function zoom(){context .setTransform(1,0,0,1,0,0); context.clearRect(0,0,canvas.width,canvas.height); context.translate(global.zoom.origin.x,global.zoom.origin.y); context.scale(global.zoom.scale,global.zoom.scale); context.translate(-global.zoom.origin.x,-global.zoom.origin.y); draw();} function trackWheel(e){if(e.deltaY> 0){if(global.zoom.scale> 0.1){global.zoom.scale * = 0.9; }} else {if(global.zoom.scale< 5){global.zoom.scale * = 1.1; }} global.zoom.scale = parseFloat(global.zoom.scale.toFixed(2));}函数trackMouse(e){global.zoom.origin.x = e.clientX; global.zoom.origin.y = e.clientY;}  

  body {背景:gainsboro;边距:0;}画布{背景:白色; box-shadow:1px 1px 1px rgba(0,0,0,.2);}  

 < canvas id = canvas>< / canvas>  



,但并没有真正的帮助。似乎使用鼠标位置作为缩放原点,但放大时会出现跳跃。






更新2



我已经设法从Blindman67的示例中隔离并简化了缩放效果,以了解其效果如何。我必须承认,我仍然不完全了解它:)我要在这里分享。将来的访问者可能会受益。



JSFiddle



  var canvas = document.getElementById( canvas ); var context = canvas.getContext( 2d); canvas.width = 600; canvas.height = 400; var zoom = {缩放比例:1,屏幕:{x:0,y:0,},世界:{ x:0,y:0,},}; var鼠标= {屏幕:{x:0,y:0,},world:{x:0,y:0,},}; var scale = {长度: function(number){return Math.floor(number * zoom.scale); },x:function(number){return Math.floor((number-zoom.world.x)* zoom.scale + zoom.screen.x); },y:function(number){return Math.floor((number-zoom.world.y)* zoom.scale + zoom.screen.y); },x_INV:function(number){return Math.floor((number-zoom.screen.x)*(1 / zoom.scale)+ zoom.world.x); },y_INV:function(number){return Math.floor((number-zoom.screen.y)*(1 / zoom.scale)+ zoom.world.y); },};函数draw(){context.clearRect(0,0,canvas.width,canvas.height); context.beginPath(); context.rect(scale.x(50),scale.y(50),scale.length(100),scale.length(100)); context.fillStyle =天蓝色; context.fill(); context.beginPath(); context.arc(scale.x(350),scale.y(250),scale.length(50),0,2 * Math.PI,false); context.fillStyle =绿色; context.fill();} canvas.addEventListener( wheel,zoomUsingCustomScale); function zoomUsingCustomScale(e){trackMouse(e); trackWheel(e); scaleShapes();} function trackMouse(e){mouse.screen.x = e.clientX; mouse.screen.y = e.clientY; mouse.world.x = scale.x_INV(mouse.screen.x); mouse.world.y = scale.y_INV(mouse.screen.y);}函数trackWheel(e){if(e.deltaY< 0){zoom.scale = Math.min(5,zoom.scale * 1.1) ; } else {zoom.scale = Math.max(0.1,zoom.scale *(1 / 1.1)); }}函数scaleShapes(){zoom.screen.x = mouse.screen.x; zoom.screen.y = mouse.screen.y; zoom.world.x = mouse.world.x; zoom.world.y = mouse.world.y; mouse.world.x = scale.x_INV(mouse.screen.x); mouse.world.y = scale.y_INV(mouse.screen.y); draw();} draw();  

  body {背景:恩斯伯勒;边距:0;}画布{背景:白色; box-shadow:1px 1px 1px rgba(0,0,0,.2);}  

 < canvas id = canvas>< / canvas>  



由于这是简化版本,因此建议您首先查看Blindman67的示例。另外,即使我接受了Blindman67的答案,您仍然可以发布答案。我觉得这个主题很有趣。因此,我想了解更多。

解决方案

如果只是缩放和平移,则解决方案很简单。 / p>

您需要跟踪两个起点。一个是鼠标在世界坐标中的位置(框和圆的位置),另一个是鼠标在屏幕坐标中的位置(画布的像素)



您需要习惯于从一个坐标系转换到另一个坐标系。这是通过反函数完成的。可以使用从屏幕坐标转换为世界坐标的逆函数反转从世界坐标到屏幕坐标。



反转一些简单函数的示例




  • 2 * 10 = 20 倒数是 20/10 = 2

  • 2 + 3 = 5 倒数是 5-3 = 2

  • (3-1)* 5 = 10 倒数是 10 *( 1/5)+ 1 = 3



乘积变为* 1。即 x * 5 变为 x * 1/5 (或只是 x / 5
加法成为减法,减法成为加法,第一个变为最后一个,最后变为第一个(3-first)* last = result 倒数是 result / last + first = 3



因此您缩放了一个坐标(框的世界坐标位置),然后获取框的屏幕位置(以像素为单位)。如果您想要屏幕像素的世界坐标,请应用反方向



这非常有意思,因此这是您的代码在做需要做的事情,并带有一些注释,我添加了mousemove,button之类的东西,因为您需要鼠标pos,并且如果无法平移则没有缩放的必要,则需要停止鼠标按钮锁定并停止滚轮滚动等等。要平移,只需移动世界原点(在代码中),请单击拖动用户界面。我也很懒,现在摆脱了 scale global.zoom.origin.x 东西,和 wx,wy sx,sy 是起源读什么的代码。



  var canvas = document.getElementById( canvas); var context = canvas.getContext( 2d ); canvas.width = 600; canvas.height = 400; //懒惰的程序员globalsvar scale = 1; var wx = 0; //世界缩放原点var wy = 0; var sx = 0; //鼠标屏幕posvar sy = 0; var mouse = {}; mouse.x = 0; // mousemouse.y = 0; mouse.rx = 0;的像素pos; //鼠标现实(世界)posmouse.ry = 0; mouse.button = 0; function zoomed(number){//只返回比例Math.floor(number * scale);} //从世界坐标转换为屏幕像素坐标zoomedX(number){//缩放& origin X return Math.floor((number-wx)* scale + sx);} function zoomedY(number){// scale&原点Y返回Math.floor((number-wy)* scale + sy);} //逆进行计算的逆过程。像(3-1)* 5 = 10一样,逆数就是10 *(1/5)+ 1 = 3 //乘以1等于* 5变成* 1/5(或只是/ 5)//加数变成减法, //反函数从屏幕像素坐标转换为世界坐标。zoomedX_INV(number){//缩放&原点INV返回Math.floor((number-sx)*(1 / scale)+ wx); //或返回Math.floor((number-sx)/ scale + wx);} function zoomedY_INV(number){// scale& origin INV return Math.floor((number-sy)*(1 / scale)+ wy); //或返回Math.floor((number-sy)/ scale + wy);} //以像素为单位绘制所有内容coordsfunction draw(){context.clearRect(0,0,canvas.width,canvas.height); context.beginPath(); context.rect(zoomedX(50),zoomedY(50),zoomed(100),zoomed(100)); context.fillStyle =天蓝色; context.fill(); context.beginPath(); context.arc(zoomedX(350),zoomedY(250),zoomed(50),0,2 * Math.PI,false); context.fillStyle =绿色; context.fill();} canvas.addEventListener( wheel,trackWheel); canvas.addEventListener( mousemove,move)canvas.addEventListener( mousedown,move)canvas.addEventListener( mouseup,move)canvas。 addEventListener( mouseout,move)//停止鼠标按钮锁定功能move(event){//鼠标移动事件,如果(event.type === mousedown){mouse.button = 1; } if 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; //获取鼠标的最后一个真实世界的位置var yy = mouse.ry; mouse.rx = zoomedX_INV(mouse.x); //通过反比例获得鼠标现实世界的位置并转换mouse.ry = zoomedY_INV(mouse.y); if(mouse.button === 1){//是向下按下鼠标按钮wx-= mouse.rx-xx; //移动世界原点一定距离//移动世界坐标wy-= mouse.ry-yy; //重新计算鼠标世界mouse.rx = zoomedX_INV(mouse.x); mouse.ry = zoomedY_INV(mouse.y); } draw();} function trackWheel(e){e.preventDefault(); //如果(e.deltaY< 0){scale = Math.min(5,scale * 1.1); //放大} else {scale = Math.max(0.1,scale *(1 / 1.1)); // //缩小是放大的倒数} wx = mouse.rx; //设置世界起源wy = mouse.ry; sx = mouse.x; //设置屏幕原点sy = mouse.y; mouse.rx = zoomedX_INV(mouse.x); //重新计算鼠标世界(真实)pos mouse.ry = zoomedY_INV(mouse.y); draw();} draw();  

  body {背景:恩斯伯勒;边距:0;}画布{背景:白色; box-shadow:1px 1px 1px rgba(0,0,0,.2);}  

 < canvas id = canvas>< / canvas>  


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 zoom/scale origin?

I suppose I need to use translate but I don't know how and where I should implement it.

What I want to use as zoom origin is the mouse position, but for simplicity, center of canvas will do.

JSFiddle

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 400;

var global = {
  zoom: {
    origin: {
      x: null,
      y: null,
    },
    scale: 1,
  },
};

function zoomed(number) {
  return Math.floor(number * global.zoom.scale);
}

function draw() {
  context.beginPath();
  context.rect(zoomed(50), zoomed(50), zoomed(100), zoomed(100));
  context.fillStyle = 'skyblue';
  context.fill();

  context.beginPath();
  context.arc(zoomed(350), zoomed(250), zoomed(50), 0, 2 * Math.PI, false);
  context.fillStyle = 'green';
  context.fill();
}

draw();

canvas.addEventListener("wheel", trackWheel);
canvas.addEventListener("wheel", zoom);

function zoom() {
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
  draw();
}

function trackWheel(e) {
  if (e.deltaY < 0) {
    if (global.zoom.scale < 5) {
      global.zoom.scale *= 1.1;
    }
  } else {
    if (global.zoom.scale > 0.1) {
      global.zoom.scale *= 0.9;
    }
  }
  global.zoom.scale = parseFloat(global.zoom.scale.toFixed(2));
}

body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}

<canvas id="canvas"></canvas>


Update 1

It seems there are few other question related to this subject on SO, but none that I can directly implement in my code.

I've tried to examine the demo Phrogz provided in Zoom Canvas to Mouse Cursor, but it's far too complex (for me, at least). Tried to implement his solution:

ctx.translate(pt.x,pt.y);
ctx.scale(factor,factor);
ctx.translate(-pt.x,-pt.y);

JSFiddle

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 400;

var global = {
  zoom: {
    origin: {
      x: null,
      y: null,
    },
    scale: 1,
  },
};

function draw() {
  context.beginPath();
  context.rect(50, 50, 100, 100);
  context.fillStyle = 'skyblue';
  context.fill();

  context.beginPath();
  context.arc(350, 250, 50, 0, 2 * Math.PI, false);
  context.fillStyle = 'green';
  context.fill();
}

draw();

canvas.addEventListener("wheel", trackWheel);
canvas.addEventListener("wheel", trackMouse);
canvas.addEventListener("wheel", zoom);

function zoom() {
  context.setTransform(1, 0, 0, 1, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);

  context.translate(global.zoom.origin.x, global.zoom.origin.y);
  context.scale(global.zoom.scale, global.zoom.scale);
  context.translate(-global.zoom.origin.x, -global.zoom.origin.y);

  draw();
}

function trackWheel(e) {
  if (e.deltaY > 0) {
    if (global.zoom.scale > 0.1) {
      global.zoom.scale *= 0.9;
    }
  } else {
    if (global.zoom.scale < 5) {
      global.zoom.scale *= 1.1;
    }
  }
  global.zoom.scale = parseFloat(global.zoom.scale.toFixed(2));
}

function trackMouse(e) {
  global.zoom.origin.x = e.clientX;
  global.zoom.origin.y = e.clientY;
}

body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}

<canvas id="canvas"></canvas>

but it didn't really help. It seems to use the mouse position as the zoom origin but there are "jumps" when I zoom in.


Update 2

I've managed to isolate and simplify the zoom effect from Blindman67's example to understand how it works better. I gotta admit, I still don't fully understand it :) I'm gonna share it here. Future visitors might benefit.

JSFiddle

var canvas    = document.getElementById("canvas");
var context   = canvas.getContext("2d");
canvas.width  = 600;
canvas.height = 400;

var zoom = {
  scale : 1,
  screen : {
    x : 0,
    y : 0,
  },
  world : {
    x : 0,
    y : 0,
  },
};

var mouse = {
  screen : {
    x : 0,
    y : 0,
  },
  world : {
    x : 0,
    y : 0,
  },
};

var scale = {
  length : function(number) {
    return Math.floor(number * zoom.scale);
  },
  x : function(number) {
    return Math.floor((number - zoom.world.x) * zoom.scale + zoom.screen.x);
  },
  y : function(number) {
    return Math.floor((number - zoom.world.y) * zoom.scale + zoom.screen.y);
  },
  x_INV : function(number) {
    return Math.floor((number - zoom.screen.x) * (1 / zoom.scale) + zoom.world.x);
  },
  y_INV : function(number) {
    return Math.floor((number - zoom.screen.y) * (1 / zoom.scale) + zoom.world.y);
  },
};

function draw() {
  context.clearRect(0, 0, canvas.width, canvas.height);

  context.beginPath();
  context.rect(scale.x(50), scale.y(50), scale.length(100), scale.length(100));
  context.fillStyle = 'skyblue';
  context.fill();

  context.beginPath();
  context.arc(scale.x(350), scale.y(250), scale.length(50), 0, 2 * Math.PI, false);
  context.fillStyle = 'green';
  context.fill();
}

canvas.addEventListener("wheel", zoomUsingCustomScale);

function zoomUsingCustomScale(e) {
  trackMouse(e);
  trackWheel(e);
  scaleShapes();
}

function trackMouse(e) {
  mouse.screen.x = e.clientX;
  mouse.screen.y = e.clientY;
  mouse.world.x  = scale.x_INV(mouse.screen.x);
  mouse.world.y  = scale.y_INV(mouse.screen.y);
}

function trackWheel(e) {
  if (e.deltaY < 0) {
    zoom.scale = Math.min(5, zoom.scale * 1.1);
  } else {
    zoom.scale = Math.max(0.1, zoom.scale * (1/1.1));
  }
}

function scaleShapes() {
  zoom.screen.x	= mouse.screen.x;
  zoom.screen.y	= mouse.screen.y;
  zoom.world.x	= mouse.world.x;
  zoom.world.y	= mouse.world.y;
  mouse.world.x	= scale.x_INV(mouse.screen.x);
  mouse.world.y	= scale.y_INV(mouse.screen.y);
  draw();
}

draw();

body {
  background: gainsboro;
  margin: 0;
}

canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}

<canvas id="canvas"></canvas>

Since this is a simplified version, I suggest you check out the Blindman67's example first. Also, even though I've accepted Blindman67's answer, you can still post an answer. I find this subject interesting. So I'd like to know more about it.

解决方案

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();
}

canvas.addEventListener("wheel", trackWheel);
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) {
  e.preventDefault(); // stop the page scrolling
  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();
}
draw();

body {
  background: gainsboro;
  margin: 0;
}
canvas {
  background: white;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, .2);
}

<canvas id="canvas"></canvas>

这篇关于设置画布缩放/缩放原点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆