我想通过使用
translate()
和 scale()
转换上下文来缩放和平移 HTML5 Canvas,清除画布,然后重新绘制。请注意,我明确地 not 在我的转换过程中调用 save()
和 restore()
。
如果我执行标准
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height)
,那么整个可见画布将不会被清除;缩小或平移可能会导致此初始矩形无法完全覆盖绘图区域。
如果我执行Webkit友好的清除方法...
var w=canvas.width;
canvas.width = 0;
canvas.width = w;
...然后上下文的累积变换被重置。
如何在不丢失转换的情况下最好地清除整个画布上下文?
像您可能正在做的那样跟踪所有转换信息是其他几个人迄今为止所做的事情(例如 cake.js 和我自己的库,两个)。我认为这样做对于任何大型画布库来说都是不可避免的。
cake.js 的 Ilmari 甚至向 mozilla 抱怨: https://bugzilla.mozilla.org/show_bug.cgi?id=408804
您可以围绕您的清除方法调用保存/恢复:
// I have lots of transforms right now
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
// Will always clear the right space
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.restore();
// Still have my old transforms
这不能满足您的需求吗?
对于那些想要跟踪完整上下文转换的人,这里是我的代码,用于按需、按上下文执行此操作。本文开头的用法展示了如何根据变换后的坐标清除整个矩形。您可以在我的网站上看到正在使用的代码。
window.onload = function(){
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
trackTransforms(ctx);
function redraw(){
var p1 = ctx.transformedPoint(0,0);
var p2 = ctx.transformedPoint(canvas.width,canvas.height);
ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y);
// ...
}
}
// Adds ctx.getTransform(), returning an SVGMatrix
// Adds ctx.transformedPoint(x,y), returning an SVGPoint
function trackTransforms(ctx){
var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg');
var xform = svg.createSVGMatrix();
ctx.getTransform = function(){ return xform; };
var savedTransforms = [];
var save = ctx.save;
ctx.save = function(){
savedTransforms.push(xform.translate(0,0));
return save.call(ctx);
};
var restore = ctx.restore;
ctx.restore = function(){
xform = savedTransforms.pop();
return restore.call(ctx);
};
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());
}
}
这是一个很老的问题,但是对于那些需要的人来说,API 在这里已经相当发展,现在这样做很简单。
首先,要清除画布,不要使用
canvas.width += 0
技巧。这在过去可能还不错,但现在画布缓冲区位于 GPU 中,这样做会带来相对明显的性能影响。有一个 reset()
方法,就像画布大小技巧一样会重置所有上下文的状态,但从我早期的测试来看,它的性能并没有非常高,所以最好的方法是清除像素缓冲区可能是使用 ctx.resetTransform()
方法重置当前变换矩阵(CTM),并使用 clearRect()
清除像素缓冲区。
getTransform()
方法,它将以 DOMMatrix
对象的形式返回 CTM。
因此,可以在重置 CTM 之前调用该方法(通过
resetTransform()
、canvas.width += 0
或 reset()
),然后将保存的 DOMMatrix()
对象传递给 setTransform()
,后者确实接受此类对象作为输入(或任何与此类对象的签名匹配的 JS 对象)。
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let direction = 1;
function anim() {
const matrix = ctx.getTransform();
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, 10, 10); // fixed point
ctx.setTransform(matrix);
// We move our rect by changing the CTM
ctx.translate(direction, 0);
ctx.fillRect(0, 50, 50, 50);
requestAnimationFrame(anim);
}
anim();
// Change direction every 2s
setInterval(() => direction *= -1, 2000);
<canvas></canvas>
但是,由于
setTransform()
可以接受 DOMMatrix
,因此您实际上可以在代码中保留这样的对象并直接使用它,而不是请求当前帧的每一帧:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const matrix = new DOMMatrix();
let direction = 1;
function anim() {
matrix.translateSelf(direction, 0);
ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, 10, 10); // fixed point
ctx.setTransform(matrix);
ctx.fillRect(10, 50, 50, 50);
requestAnimationFrame(anim);
}
anim();
// Change direction every 2s
setInterval(() => direction *= -1, 2000);
<canvas></canvas>