在研究CSS3动画性能的时候,看到了重排两个字。
突然想到自己虽然听说过这么个东东,但一直也没深入研究之。
趁着当下正好有研究的劲头,所以一不做二不休,把这个point也给学习了。
同样是一番查找资料...不过这次我得跟作者和原文say sorry了...
因为...我在整理的过程中没注意mark下原文的url...so~~~
主会原谅我的...
浏览器的工作流程
1.浏览器会解析三个东西:
HTML/SVG/XHTML,事实上,Webkit 有三个 C++ 的类对应这三类文档。解析这三种文件会产生一个 DOM Tree。
CSS,解析 CSS 会产生 CSS Rule Tree。
Javascript脚本,主要是通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。
2.解析完成后,浏览器引擎会通过 DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。
注意:Rendering Tree 并不等同于 DOM Tree,因为一些像 Header 或 display:none 的东东是不会出现在渲染树中的。
CSS Rule Tree 主要是为了完成匹配并把 CSS Rule 附到 Rendering Tree 上的每个 Element。也就是 DOM 结点。也就是所谓的 Frame。
然后,计算每个 Element 的位置,这又叫 layout 和 reflow 过程。
3.通过调用操作系统 Native GUI 的 API 绘制。
重绘和重排的概念
Repaint —— 重绘。屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
Reflow —— 重排。意味着元件的几何尺寸变了,我们需要重新验证并计算 Render Tree。是 Render Tree 的一部分或全部发生了变化。这就是 Reflow,或是 Layout。(HTML 使用的是 flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫 reflow)reflow 会从 <html> 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置,在 reflow 过程中,可能会增加一些 frame,比如一个文本字符串必需被包装起来。
重排的成本比重绘的成本高得多的多。DOM Tree 里的每个结点都会有 reflow 方法,一个结点的 reflow 很有可能导致子结点,甚至父点以及同级结点的 reflow。在一些高性能的电脑上也许还没什么,但是如果 reflow 发生在手机上,那么这个过程是非常痛苦和耗电的。
多说两句关于滚屏的事,通常来说,如果在滚屏的时候,我们的页面上的所有的像素都会跟着滚动,那么性能上没什么问题,因为我们的显卡对于这种把全屏像素往上往下移的算法是很快。但是如果你有一个 fixed 的背景图,或是有些 Element 不跟着滚动,有些 Elment 是动画,那么这个滚动的动作对于浏览器来说会是相当相当痛苦的一个过程。你可以看到很多这样的网页在滚动的时候性能有多差。因为滚屏也有可能会造成 reflow。
由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。但 table 及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用 table 做布局的一个原因。此外,使用 table 的话,可能很小的一个小改动会造成整个 table 的重新布局。
常见的触发重排的操作
1.DOM元素的几何属性变化。
当DOM元素的几何属性变化时,渲染树中的相关节点就会失效,浏览器会根据DOM元素的变化重建构建渲染树中失效的节点。之后,会根据新的渲染树重新绘制这部分页面。而且,当前元素的重排也许会带来相关元素的重排。例如,容器节点的渲染树改变时,会触发子节点的重新计算,也会触发其后续兄弟节点的重排,祖先节点需要重新计算子节点的尺寸也会产生重排。最后,每个元素都将发生重绘。可见,重排一定会引起浏览器的重绘,一个元素的重排通常会带来一系列的反应,甚至触发整个文档的重排和重绘,性能代价是高昂的。
2.DOM树的结构变化。
当DOM树的结构变化时,例如节点的增减、移动等,也会触发重排。浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。通常在这个过程中,当前元素不会再影响其前面已经遍历过的元素。所以,如果在body最前面插入一个元素,会导致整个文档的重新渲染,而在其后插入一个元素,则不会影响到前面的元素。
3.获取某些属性。
浏览器引擎可能会针对重排做了优化。比如Opera,它会等到有足够数量的变化发生,或者等到一定的时间,或者等一个线程结束,再一起处理,这样就只发生一次重排。但除了渲染树的直接变化,当获取一些属性时,浏览器为取得正确的值也会触发重排。这样就使得浏览器的优化失效了。
这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用这些值时应进行缓存。
此外,改变元素的一些样式,调整浏览器窗口大小等等也都将触发重排。
减少重排次数和缩小重排影响范围的一些方法
1.将多次改变样式属性的操作合并成一次操作。例如:
<script> var changeDiv = document.getElementById(‘changeDiv‘); changeDiv.style.color = ‘#093‘; changeDiv.style.background = ‘#eee‘; changeDiv.style.height = ‘200px‘; </script>
可以合并为:
<style> div.changeDiv { background: #eee; color: #093; height: 200px; } </style> <script> document.getElementById(‘changeDiv‘).className = ‘changeDiv‘; </script>
因为多次改变属性的操作,当中包含N个会触发重排的属性改变,就会触发N次重排。将其合并后,就只触发一次了。
2.将需要多次重排的元素,position 属性设为 absolute 或 fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
3.在内存中多次操作节点,完成后再添加到文档中去。
例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html片段,再一次性添加到文档中去,而不是循环添加每一行。
4.由于 display 属性为 none 的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。
如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。
5.在需要经常取那些引起浏览器重排的属性值时,要缓存到变量。
这些属性包括:offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。
所以,在多次使用这些值时应进行缓存。
6.不要在css里面写expression,如果css里有expression,很小的一个行为都有可能重新计算一遍。