前端“油画设计师”——双缓存绘制与油画分层机制

背景

Canvas在图像处理、绘制渲染上有一些得天独厚的优势。但是当我们当前展示的内容中在主题内容变化不大的情况下,会有一些小部分内容的变化,在页面刷新或者滚动的时候,一帧中会有很多复杂内容元素的图画运算,重新对页面元素绘制会导致CPU使用率飙升。

而重新绘制的过程,实质上是一个不断刮白-重画的过程。但在屏幕上完成这一系列操作是需要一定时间的,而且屏幕上的图形越复杂,所花的时间就越长,我们肉眼可见的刮白-重画操作,在使用过程中就会让就会直接感觉到屏幕的闪烁。

重绘带来的性能负担和闪烁的问题,会给使用者带来较差的使用体验。为了更好的优化这个两个问题,出现了双缓存画布和油画分层的绘制方法。而本节内容我们也将从电子表格技术出发,为大家揭秘在电子表格技术中双缓存与优化技术的具体应用。

双缓存画布

现在我们有一幅图需要放在Canvas中,使用drawImage()方法,有三种写法:

// 将image放到目标canvas指定位置

void ctx.drawImage(image, dx, dy); 

// 将image放到目标canvas指定位置,指定宽高渲染

void ctx.drawImage(image, dx, dy, dWidth, dHeight);

// 将image裁剪之后放到目标canvas指定位置,指定宽高渲染

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

第一种方法只是把图片原样放到Canvas中,第二种方法指定宽高就意味着放大或者缩小图片后再放进去,第三种是将图片裁剪后再放大或者缩小放到canvas中,这三种写法操复杂度作依次增加,性能开销也随之增大。

而如果使用离屏渲染(即我们所说的双缓存画布),我们可以预先把图片裁剪成想要的尺寸,然后将该内容保存起来,绘制的时候直接使用第一种写法直接将图片放入Canvas中。

// 在离屏 canvas 上绘制

var offscreencanvas = document.createElement('canvas');

// 宽高赋值为想要的图片尺寸

offscreencanvas.width = dWidth;

offscreencanvas.height = dHeight;

// 裁剪

offscreencanvas.getContext('2d').drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);

// 在视图canvas中绘制

viewcontext.drawImage(canvas, x, y);

双缓存画布技术的核心在于系统需要在内存中开辟一块与当前画面等大的“逻辑屏幕“。我们的画图和动画操作都会先作用于这块”逻辑屏幕“中,当一个操作在这块”逻辑屏幕“上完成之后,再把整块”逻辑屏幕“投放到我们的屏幕上。

(离屏渲染原理示意图)

在这样的过程之下,我们是无法看到整个图形在屏幕上的重绘过程,从而解决了闪烁问题。就好像看动漫一样,不用双缓存技术,就是画一帧看一帧,肯定会卡顿。而用了双缓存技术,会事先把每一帧画好,不断翻动展示出来。

(逐帧动画)
Canvas为此提供了OffscreenCanvas方法,用来构建一个可以脱离屏幕渲染的canvas对象,它在窗口环境和web worker环境均有效。对于一些渲染,如果创建 Image 再进行渲染,会消耗大量 CPU,但用离屏渲染,实测在高频事件中 CPU 使用率减少了一倍之多。

油画分层绘制

分层渲染来处理画面动画的思路并不是现在才有的,从非物质文化遗产皮影戏、套色印刷技术,到现在的音影工业等众多领域都有频繁出现, 而这种思想在Canvas中也处在基石的地位。

(分层渲染原理示意图)

Canvas分层的思想是,动画中每种元素,对渲染和动画的要求是不一样的。

用下图举个例子,在这张图片中除了猫本身在运动外,背景以及下方的文字都是静止重复的。

(油画分层机制示意图)

按照分层的逻辑,我们需要频繁更新绘制的只有最上方的猫咪。这个方法类似油画的绘制,所以也被称为油画分层机制。使用这个方法结合双缓冲技术可以有效的将重复绘制的内容分流到屏幕外的画布上,然后再根据我们的需求将屏幕外图像渲染到主画布上,省去了频繁生成重复部分的步骤。

技术应用落地

在实际应用中需要在前端对复杂内容进行渲染或者处理大量数据时,为了更好地对性能进行优化,现在已经有很多项目实际采用了Canvas的双缓存画布和油画分层技术。我们在做电子表格技术选型时也考虑到了这些问题,在电子表格应用项目中,我们动辄需要处理百万数量级数据内容,这种情况下浏览器对表格内容渲染和数据处理的性能就显得无比重要。

上图是纯前端电子表格中50000*20=100000个数据,处理只需要0.038s。在该纯前端电子表格中,整个绘制引擎根据油画绘制原理,分为主体图层和装饰图层,主题图层将会渲染持久的,不会轻易改变的元素,例如背景,单元格,表格线等。而装饰图层则会渲染常变性元素,例如选择框,拖拽框,悬浮效果等。在下图中第一层到第四层都是主体图层的内容,第五层是装饰图层。

除此之外整个的绘制过程并不是从数据层(Model)直接到视图层(View)的。而是根据表格内容的特殊性,实现了根据视图层形状,从数据层组合出一层专属视图层的视图数据(ViewModel),再配合前文提到的双缓存画布绘制机制,完成整个表格按需绘制的需求,并缓存绘制结果,进一步提升绘制性能。

主体图层不是直接绘制在用户能看到的主画布上,而是绘制在一个看不见的缓存画布上。在需要渲染时,只需要讲缓存画布的内容克隆到主画布上,再附加上装饰图层元素

这样,当表格需要更新时候,比如单元格背景改变,只需要在克隆缓存画布后重绘对应单元格内容即可。

而当表格向下滚动时,表格滚动结束,需要重绘,主画布会被清空,然后从缓存画布中根据行为上下文进行画布偏移,将偏移后的图层直接绘制在主画布上,随后在主画布上绘制偏移后的剩余部分,最后更新缓存。

使用缓存画布和油画分层机制,大大提升了绘制性能,使整个滚动过程更加流畅、顺滑。

觉得不错给点个赞吧~后续还会为大家带来更多技术揭秘和有趣内容。

拓展阅读

posted @ 2021-09-01 10:33  葡萄城技术团队  阅读(4726)  评论(7编辑  收藏  举报