浏览器渲染机制的复习和整理,主要来自酷壳网和黑色影子在segmengful的文章,对于层和复合层的概念还需要进一步加深学习理解。链接附在文末。
解析生成DOM Tree/CSS Rule tree
1)浏览器会解析三个东西:
- 一个是HTML/SVG/XHTML,产生一个DOM Tree。
- CSS,解析CSS会产生CSS规则树。
- Javascript,脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.
2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意:
Rendering Tree 渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了。
CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame。
然后,计算每个Frame(也就是每个Element)的位置,这又叫layout和reflow过程。
3)最后通过调用操作系统Native GUI的API绘制。
渲染的流程基本上如下(黄色的四个步骤):
- 计算CSS样式
- 构建Render Tree
- Layout – 定位坐标和大小,是否换行,各种position, overflow, z-index属性 ……
- 正式开画
注意:上图流程中有很多连接线,这表示了Javascript动态修改了DOM属性或是CSS属会导致重新Layout,有些改变不会,就是那些指到天上的箭头,比如,修改后的CSS rule没有被匹配到,等。
这里重要要说两个概念,一个是Reflow,另一个是Repaint。这两个不是一回事。
Repaint——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
Reflow——意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow)reflow 会从<html>这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必需被包装起来。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。
减少reflow/repaint
下面是一些Best Practices:
下面是一些针对reflow和repaint的最佳实践:
不要一条一条地修改dom的样式,尽量使用className一次修改。
-
将dom离线后修改
使用documentFragment对象在内存里操作dom。
先把dom节点display:none;(会触发一次reflow)。然后做大量的修改后,再把它显示出来。
clone一个dom节点在内存里,修改之后;与在线的节点相替换。
不要使用table布局,一个小改动会造成整个table的重新布局。
transform和opacity只会引起合成,不会引起布局和重绘。
从上述的最佳实践中你可能发现,动画优化一般都是尽可能地减少reflow、repaint的发生。关于哪些属性会引起reflow、repaint及composite,你可以在这个网站找到https://csstriggers.com/。
Composite
在reflow和repaint之后,浏览器会将多个复合层传入GPU;进行合成工作,那么合成是如何工作的呢?
GPU实际上可以看作一个独立的计算机,它有自己的处理器和存储器及数据处理模型。当浏览器向GPU发送消息的时候,就像向一个外部设备发送消息。
浏览器向GPU发送数据也需要先创建一个载体;只不过GPU距离CPU很近,不会像远程服务器那样可能几千里那么远。但是对于远程服务器,2秒的延迟是可以接受的;但是对于GPU,几毫秒的延迟都会造成动画的卡顿。
浏览器向GPU发送的数据载体是什么样?这里给出一个简单的制作载体,并把它们发送到GPU的过程。
画每个复合层的图像
准备图层的数据
准备动画的着色器(如果需要)
向GPU发送数据
为了仅发生composite,我们做动画的css property必须满足以下三个条件:
不影响文档流。
不依赖文档流。
不会造成重绘。
满足以上以上条件的css property只有transform和opacity。你可能以为position也满足以上条件,但事实不是这样,举个例子left属性可以使用百分比的值,依赖于它的offset parent。还有em、vh等其他单位也依赖于他们的环境
浏览器在动画执行之前就知道动画如何开始和结束,因为浏览器没有看到需要reflow和repaint的操作;浏览器就会画两张图像作为复合层,并将它们传入GPU。
这样做有两个优势:
动画将会非常流畅
动画不在绑定到CPU,即使js执行大量的工作;动画依然流畅
优化技巧总结
减少浏览器的重排和重绘的发生。
不要使用table布局。
css动画中尽量只使用transform和opacity,这不会发生重排和重绘。
尽可能地只使用css做动画。
css动画有一个重要的特性,它是完全工作在GPU上。因为你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好所有需要的指令;并把它们发送给GPU。而如果使用js动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,我们必须在浏览器主线程计算新状态;把它们发送给GPU至少60次每秒。除了计算和发送数据比css动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会造成动画的延迟、卡顿。
所以尽可能地使用基于css的动画,不仅仅更快;也不会被大量的js计算所阻塞。
避免浏览器的隐式合成。
改变复合层的尺寸
参考:
https://segmentfault.com/a/1190000008015671
https://coolshell.cn/articles/9666.html/comment-page-2#comments