[源码分析] canvas 实现缩放和拖拽
ZhenHe17 opened this issue · comments
GeShengming commented
需求
通过鼠标滚轮来缩放 canvas ,通过鼠标拖拽 canvas 。
这个需求的讨论来源于 Stack Overflow 上的问题 Zoom Canvas to Mouse Cursor ,作者使用了 SVG 的 createSVGMatrix 来记录 canvas 变换的矩阵,从而完善了缩放和拖拽的效果,这个思路引起了我的兴趣,这里对源码进行些分析。
源码分析
整体思路
- 通过监听 mousedown 、mousemove 、mouseup 触发拖拽的行为;通过监听 mousewheel 、触发滚轮缩放的行为
- 通过 canvas 的 translate 方法来移动画布;scale 方法缩放画布
- 实现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 上的点击事件的位置,可以通过矩阵变换对应到变换后的绘图上的位置。