HTML5 Canvas clearRect()在安卓Chrome中闪烁问题求助
Android Canvas局部清除后渲染闪烁问题
我有两个相同的Canvas元素:
<canvas id="canvas1" width="512" height="512" style="background:#eeeeff"></canvas> <canvas id="canvas2" width="512" height="512" style="background:#eeffee"></canvas>
下面的代码会每帧向每个Canvas渲染图像:
var canvas1 = document.getElementById('canvas1'); var canvas2 = document.getElementById('canvas2'); var context1 = canvas1.getContext('2d'); var context2 = canvas2.getContext('2d'); var image = new Image(); image.src = 'http://colrd.com/misc/library/pattern/DinPattern/horton.gif'; var draw = function(context) { context.clearRect(128, 128, 256, 256); // 此操作在安卓设备上引发闪烁 // context.clearRect(0, 0, 512, 512); // 若清除整个画布则一切正常 context.drawImage(image, 128, 128, 256, 256); }; var onDraw1 = function() { draw(context1); requestAnimationFrame(onDraw1, canvas1); }; var onDraw2 = function() { draw(context2); requestAnimationFrame(onDraw2, canvas2); }; onDraw1(); onDraw2();
这段代码在主流桌面浏览器中运行正常,但在Android Chrome及安卓自带浏览器中会出现明显闪烁;如果换成清除整个画布(注释掉的那行代码),闪烁就消失了。另外只用单个Canvas时也能重现问题,但闪烁现象没那么明显。
请问我哪里操作有误?应该怎么解决这个问题?
解决方案
问题根源
这其实是Android浏览器Canvas渲染管线的一个已知行为:当你只清除画布的局部区域时,浏览器的合成器可能无法正确处理局部更新的"脏区域",导致帧与帧之间的缓冲区出现不一致,进而产生闪烁。尤其是当多个Canvas同时进行局部更新时,这种渲染冲突会被放大,闪烁表现更明显。
另外,requestAnimationFrame的第二个参数(指定关联元素)在部分移动端浏览器的实现中支持并不完善,这也可能加剧渲染同步的问题。
修复方案
1. 改用离屏Canvas做预渲染(推荐)
创建一个离屏Canvas,先在离屏Canvas上完成所有绘制操作(清除+绘制图像),再把整个离屏Canvas绘制到目标Canvas上。这样目标Canvas每次都是整幅更新,从根源避免局部清除带来的渲染问题:
var canvas1 = document.getElementById('canvas1'); var canvas2 = document.getElementById('canvas2'); var context1 = canvas1.getContext('2d'); var context2 = canvas2.getContext('2d'); // 创建离屏Canvas var offscreenCanvas = document.createElement('canvas'); offscreenCanvas.width = 512; offscreenCanvas.height = 512; var offscreenCtx = offscreenCanvas.getContext('2d'); var image = new Image(); image.src = 'http://colrd.com/misc/library/pattern/DinPattern/horton.gif'; var draw = function(targetContext) { // 先在离屏Canvas上完成绘制 offscreenCtx.clearRect(128, 128, 256, 256); offscreenCtx.drawImage(image, 128, 128, 256, 256); // 把离屏Canvas整体绘制到目标Canvas targetContext.drawImage(offscreenCanvas, 0, 0); }; var onDraw1 = function() { draw(context1); requestAnimationFrame(onDraw1); }; var onDraw2 = function() { draw(context2); requestAnimationFrame(onDraw2); }; // 确保图像加载完成后再启动渲染 image.onload = function() { onDraw1(); onDraw2(); };
2. 统一清除并重绘整个画布
如果画布其他区域是静态背景,可以提前绘制一次背景,之后每次重绘时先清除整个画布,再重绘背景+目标图像。这样既避免闪烁,也不会额外增加太多性能开销:
var canvas1 = document.getElementById('canvas1'); var canvas2 = document.getElementById('canvas2'); var context1 = canvas1.getContext('2d'); var context2 = canvas2.getContext('2d'); var image = new Image(); image.src = 'http://colrd.com/misc/library/pattern/DinPattern/horton.gif'; // 初始化时绘制静态背景 context1.fillStyle = '#eeeeff'; context1.fillRect(0,0,512,512); context2.fillStyle = '#eeffee'; context2.fillRect(0,0,512,512); var draw = function(context) { context.clearRect(0, 0, 512, 512); // 清除整个画布 // 重绘背景 context.fillRect(0,0,512,512); context.drawImage(image, 128, 128, 256, 256); }; var onDraw1 = function() { draw(context1); requestAnimationFrame(onDraw1); }; var onDraw2 = function() { draw(context2); requestAnimationFrame(onDraw2); }; image.onload = function() { onDraw1(); onDraw2(); };
3. 优化渲染同步
去掉requestAnimationFrame的第二个参数,改用单个动画循环来同时更新两个Canvas,确保两个Canvas的渲染同步进行,减少合成冲突:
// 替换原来的两个独立渲染循环为统一循环 var renderLoop = function() { draw(context1); draw(context2); requestAnimationFrame(renderLoop); }; image.onload = function() { renderLoop(); };
内容的提问来源于stack exchange,提问作者Fr1ar
相关产品推荐
相关产品推荐

