ZhenHe17 / blog

个人博客,希望能让各位看官有所收获,喜欢可以 star || watch ^_^ 🎉

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[源码分析] canvas 实现缩放和拖拽

ZhenHe17 opened this issue · comments

需求

通过鼠标滚轮来缩放 canvas ,通过鼠标拖拽 canvas 。

这个需求的讨论来源于 Stack Overflow 上的问题 Zoom Canvas to Mouse Cursor ,作者使用了 SVG 的 createSVGMatrix 来记录 canvas 变换的矩阵,从而完善了缩放和拖拽的效果,这个思路引起了我的兴趣,这里对源码进行些分析。

在线例子
源码

源码分析

整体思路

  1. 通过监听 mousedown 、mousemove 、mouseup 触发拖拽的行为;通过监听 mousewheel 、触发滚轮缩放的行为
  2. 通过 canvas 的 translate 方法来移动画布;scale 方法缩放画布
  3. 实现trackTransforms,以保存每一次操作后的转换矩阵

canvas 的 translate、 scale 方法可以自由移动缩放画布,但是无法记录当前的变换状态,导致不能在绘制 canvas 时再使用 translate、 scale 方法等;并且不能获取 canvas 点击事件在矩阵变换后的位置。所以作者对每次变换进行记录,解决了变换状态丢失的问题。

实现 trackTransforms

trackTransforms 在源码中的位置:

125 | function trackTransforms(ctx){
......
178 | }

创建矩阵保存变换状态

trackTransforms 先利用 SVG 对象的 createSVGMatrix 创建了矩阵,这个矩阵保存着所有 canvas 的缩放、倾斜和移动:

var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg');
var xform = svg.createSVGMatrix();

修改变换相关的方法

对 canvas 中变换相关的方法进行修改,使每次造成 canvas 变换的操作也对矩阵造成了相应的变换。这样一来就可以通过矩阵记录当前的变换状态,即:变换前的点 -> 矩阵 -> 变换后的点。矩阵的具体计算方法见MDN文档

var scale = ctx.scale;
ctx.scale = function(sx,sy){
  xform = xform.scaleNonUniform(sx,sy);
  return scale.call(ctx,sx,sy);
};
var rotate = ctx.rotate;
ctx.rotate = function(radians){
  xform = xform.rotate(radians*180/Math.PI);
  return rotate.call(ctx,radians);
};
var translate = ctx.translate;
ctx.translate = function(dx,dy){
  xform = xform.translate(dx,dy);
  return translate.call(ctx,dx,dy);
};
var transform = ctx.transform;
ctx.transform = function(a,b,c,d,e,f){
  var m2 = svg.createSVGMatrix();
  m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f;
  xform = xform.multiply(m2);
  return transform.call(ctx,a,b,c,d,e,f);
};
var setTransform = ctx.setTransform;
ctx.setTransform = function(a,b,c,d,e,f){
  xform.a = a;
  xform.b = b;
  xform.c = c;
  xform.d = d;
  xform.e = e;
  xform.f = f;
  return setTransform.call(ctx,a,b,c,d,e,f);
};

通过矩阵获取变换后的点的坐标

var pt  = svg.createSVGPoint();
ctx.transformedPoint = function(x,y){
  pt.x=x; pt.y=y;
  return pt.matrixTransform(xform.inverse());
}

比如作用在 canvas 上的点击事件的位置,可以通过矩阵变换对应到变换后的绘图上的位置。

其他实现此需求的JS库