【Fanvas技术解密】HTML5 canvas实现脏区重绘
先说明一下,fanvas是笔者在企鹅公司开发的,即将开源的flash转canvas工具。
脏区重绘(dirty rectangle)并不是一门新鲜的技术了,这在最早2D游戏诞生的时候就已经存在。
复杂的术语或概念就不多说,简单说,脏区重绘就是每一帧绘制图形界面的时候,只重新绘制有变化的区域,而不是全屏刷新。很明显,这肯定能带来性能的提升。
举个例子,看下边两个图:

假设这里是动画的连续2帧,那么从第一帧到第二帧,其实变化的只有蝴蝶的区域。那么所谓的脏区就是两个图片的红色框之和,要把上一帧的蝴蝶擦掉,然后把新区域的蝴蝶位置也擦掉,接着才能绘制新的背景和蝴蝶。这相比整屏重绘,重绘的面积小了几十倍,由于canvas 2d使用的是CPU处理,那么相应地,CPU处理的像素个数就少了很多倍,顺理成章,动画的效率就会提高。
看起来非常简单,大概来说,只需要2步:
1、找出这一帧变化的矩形区域;
2、利用canvas的api实现脏区重绘。
但是,问题来了,怎么计算变化区域呢?canvas又是否提供了现成的接口呢?我们拿上述蝴蝶的例子,逐步来看看。
首先,我们看关于脏区的计算。
如果动画非常简单,没有使用“显示列表”,所有图案都是一层绘制的,那么“也许”绘制者,也就是开发者了,可能会知道蝴蝶的位置,然后手工指定重绘的区域。呃。。。等等,好像有点什么问题,不可能每次都手工指定重绘的区域!!!
再看看Fanvas里的情况,Fanvas采用了显示列表,把图案拆分为多个元件,元件和元件之间以“显示列表”的形式组织起来,这参考了Flash的技术。这里,蝴蝶被封装为一个Shape,蝴蝶在画面飞舞,抽象为Shape在父元件中移动、旋转。最初,在Shape中绘制蝴蝶的时候,可能占据的矩形区域是(x:0,y:0,width:100,height:50),这里参考的是Shape内部的坐标系(还没放到舞台上)。然后,蝴蝶被添加到舞台上时,需要位移和旋转,例如做了(x:400,y:100)的位移,和旋转了60度。这时候如何计算新的矩形呢?

这个过程其实就是局部坐标系映射到全局坐标系的问题,涉及到矩阵计算,可以参考我之前写的文章,这里就不多说了。http://km.oa.com/articles/show/238103。另外,提一下,这里其实还有一个难点,初始绘制时(x:0,y:0,width:100,height:50),这个矩形是如何计算得到的呢?如果绘制的是一个图片,那当然好计算;如果是一系列的矢量线条,这个就略麻烦了,不过这个不在这里讨论了,因为Fanvas是Flash导出Canvas动画,在导出的时候Flash自带了这个矩阵信息。
上述的计算都在一个前提情况下:我们已知蝴蝶是唯一一个变化的元件,但在实际动画过程中,如何自动识别变化的内容呢?
要从动画的原理说起,动画过程无非分为4种操作:
1. 新建一个元件(例如蝴蝶),添加到舞台上;
2. 移动、旋转、放缩原有的元件;
3. 删除已有的元件;
4. 修改元件的遮罩关系,这点有点特殊,如果对flash动画不熟悉的同学可能不大理解,不过不重要,我们知道有这回事就可以了,不影响文章的继续阅读。
那么,在Fanvas中,我们就需要对上述4种情况分别处理。
1. 新建:只有1个脏矩形,就是这个元件本身;
2. 移动/旋转/放缩:元件上一帧的矩形区域是脏区,新一帧的矩形区域也是脏区;
3. 删除:跟新建情况一样;
4. 遮罩变化:跟2一样。
理清楚这些细节之后,如何实现就比较好办了,无非就是每一帧绘制前把脏区列表情况,然后计算出所有脏区矩形,再开始绘制。
接着,我们再来看第二步,canvas如何具体操作,是否有脏区重绘接口?
其实,canvas并没有真正的脏区重绘接口,不过有一个clip,这个一般用于实现遮罩,不过也可以取巧的用来实现脏区重绘。经笔者测试,简单使用clip虽然性能优化不是太明显,但还是有20%的提升的。再复杂一些,当然大家可以自行根据脏区列表,重写每个元件的绘制方法,自行实现脏区重绘,不过笔者估计啊,js写这么多逻辑,最终还是吃力不讨好。
我们来看看代码:
for (var i = ; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.clearRect(rect.x, rect.y, rect.width, rect.height);
}
ctx.beginPath();
for (var i = ; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.rect(rect.x, rect.y, rect.width, rect.height);
}
ctx.clip();
相信变量名已经很明显的暴露了自己的用途,大家应该明白,实现脏区重绘非常简单,只需要在全部绘制前加那么一段clip,搞掂。
不过啊,这里可有一个很大的坑,估计有同学也知道。

正如上图所示,会出现一些1px白线或者没清干净的bug,尤其是舞台本身有拉伸的情况下,这种bug更明显。经过笔者多次摸索,大概搞清楚了,主要就是脏区要算仔细(如果舞台有拉伸,很容易算出来有1、2px差别),画面要等比例拉伸,另外就是清除和重绘时,大方点,给1px的放宽。
最后变成:
for (var i = 0; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.clearRect(rect.x-1, rect.y-1, rect.width+2, rect.height+2);
}
ctx.beginPath();
for (var i = 0; i < dirtyRectList.length; i++) {
var rect = dirtyRectList[i];
ctx.rect(rect.x-1, rect.y-1, rect.width+2, rect.height+2); }
ctx.clip();
至此,Fanvas脏区重绘的秘密就彻底曝光了。。。
最后来看看实际的效果(第一张是没有使用脏区重绘,第二张使用脏区重绘):



当然,这并不是每个动画都有效果,因为一些动画本来就大范围变化,所以Fanvas针对这些情况也做了兼容处理,如果发现脏区太多或者面积太大,就继续使用原来的全屏重绘。
【Fanvas技术解密】HTML5 canvas实现脏区重绘的更多相关文章
- 【HTML5 Canvas】计算元件/显示对象经过Matrix变换后在上级/舞台上的bounds(边界矩形rect)
如上图所示,这样的一个简单矩形,边界矩形是(x:-28, y:-35, width:152, height:128),这是在这个元件/显示对象自己的坐标空间的范围. 那么把这个放到父元件(舞台)中,再 ...
- 通过分析 WPF 的渲染脏区优化渲染性能
原文:通过分析 WPF 的渲染脏区优化渲染性能 本文介绍通过发现渲染脏区来提高渲染性能. 本文内容 脏区 Dirty Region WPF 性能套件 脏区监视 优化脏区重绘 脏区 Dirty Regi ...
- html5 Canvas和SVG的区别是什么(总结)
html5 Canvas和SVG的区别是什么(总结) 一.总结 一句话总结:都是2D做图,svg是矢量图,canvas是位图.Canvas 是逐像素进行渲染的,适合游戏. 1.svg的全称是什么? S ...
- html5 canvas的教程
原文地址:http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html 原作很强悍 导航 前言 基本知识 绘制矩形 清除矩形区域 圆弧 路 ...
- HTML5 canvas画图
HTML5 canvas画图 HTML5 <canvas> 标签用于绘制图像(通过脚本,通常是 JavaScript).不过,<canvas> 元素本身并没有绘制能力(它仅仅是 ...
- html5 canvas 详细使用教程 转
分类: html5(9) 原文地址:http://www.cnblogs.com/tim-li/archive/2012/08/06/2580252.html 原作很强悍 导航 前言 基本知识 绘 ...
- html5和html的区别是什么(精问)
html5和html的区别是什么(精问) 一.总结 一句话总结:html5:简洁(文档生命,链接引入) 语义化(语义化标签) API(canvas,地理位置等) 一些标签(input新类型) 二. ...
- HTML5 程序设计 - 使用HTML5 Canvas API
请你跟着本篇示例代码实现每个示例,30分钟后,你会高喊:“HTML5 Canvas?!在哥面前,那都不是事儿!” 呵呵.不要被滚动条吓到,很多都是代码和图片.我没有分开写,不过上面给大家提供了目录,方 ...
- 赠书:HTML5 Canvas 2d 编程必读的两本经典
赠书:HTML5 Canvas 2d 编程必读的两本经典 这两年多一直在和HTML5 Canvas 打交道,也带领团队开发了世界首款基于HTML5 Canvas 的演示文档工具---AxeSlide( ...
随机推荐
- [Web 前端] ECMAScript5之StrictMode
cp from : https://www.cnblogs.com/giggle/p/5252185.html ECMAScript5引入一个严格模式的概念(Strict Mode). 它的作用就是不 ...
- python的重试库tenacity用法以及类似库retry、requests实现
介绍 tenacity is an Apache 2.0 licensed general-purpose retrying library, written in Python, to simpli ...
- 【转载】Qt Creator 添加自定义注释
原文:https://blog.csdn.net/liang19890820/article/details/54925434 自定义注释注释的原则是有助于对程序的阅读理解,不宜太多,也不能太少(一般 ...
- JS 判断上传 文件 大小
随着HTML5 的发展,我们可以用file控件的size属性来获取客户端 上传文件的大小,但是 我今天测试 发现IE10支持,IE11的某个版本不支持, 于是就借用img控件来加载一此, 以此来获取文 ...
- Server 2008 R2大改造变成梦幻Win7系统
在此之前先补充一下知识Windows Server 2008和Windows Server 2008 R2的不同之处Windows Server 2008是基准与Vista的内核构建的,支持X86框架 ...
- Commands to help you to Start Using ScaleIO Storage
To start using your storage: Log in to the MDM: scli --login --username admin --password <passwor ...
- 深入理解Javascript window对象
首先看我们的源代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> ...
- C#与Visual Basic的异与同
C#是一种高级程序设计语言,是一种安全.稳定.简单.优雅的编程语言,它与Visual Basic有很多相同的地方,同时也有很多不同的地方.我们今天这篇博客本着学习C#的原则,着重介绍一下C#与Visu ...
- 设置虚拟机IP
重启虚拟机 重启后,查看虚拟机IP ip a 虚拟机已有自己的IP 输入虚拟机用户
- Java——DOM方式生成XML (转)
http://blog.csdn.net/u012325167/article/details/50943202 使用DOM方式生成XML文件有如下几步: 首先是创建DOM树(即规定XML文件中的内容 ...