回流与重绘:CSS性能让JavaScript变慢?

目前有有关此文章的介绍(这里),但是只是部分内容。本文为全文翻译,并提供其他的一些页面性能优化方面的资源,希望能对reflow的进一步认识有所帮助。我之前还翻译过一篇关于reflow的文章,您有兴趣可以看看(点击这里)。

以下为翻译全文,以”zxx:”打头的灰色文字是译者,也就是的一些旁白了。

//正文开始

关于回流(reflows)与重绘(repaints),我已经在twitterdelicious上发布,但是并没有在演讲中提到或是以文章形式发布。

第一次让我开始思考关于回流(reflows)与重绘(repaints)的问题是在和ParisWeb上的Mr. Glazman做一个firey交换的时候。我可能有一些顽固,但是我确实听了他的一些理论。:)Stoyan和我开始讨论如何量化这个问题。

展望性能社区,除了一些典型的黑盒实验外,需要与浏览器厂商有更多的合作。对于性能,浏览器制造者知道哪些是重要的,哪些是不相干的。Opera列出“reflow和repaint是减缓JavaScript的三大主要原因之一”一文,所以其肯定值得一看。// zxx: Firefox浏览器相关内容可以看这里;Safari可以看这里

让我们从一些背景资料开始,当一个元素的外观的可见性visibility发生改变的时候,重绘(repaint)也随之发生,但是不影响布局。类似的例子包括:outline, visibility, or background color。根据Opera浏览器,重绘的代价是高昂的,因为浏览器必须验证DOM树上其他节点元素的可见性。而回流更是性能的关键因为其变化涉及到部分页面(或是整个页面)的布局。一个元素的回流导致了其所有子元素以及DOM中紧随其后的祖先元素的随后的回流。

例如:

<body>
<div class="error">
	<h4>我的组件</h4>
	<p><strong>错误:</strong>错误的描述…</p>
	<h5>错误纠正</h5>
	<ol>
		<li>第一步</li>
		<li>第二步</li>
	</ol>
</div>
</body>

在上面的HTML片段中,对该段落(<p>标签)回流将会引发强烈的回流,因为它是一个子节点。这也导致了祖先的回流(div.error和body – 视浏览器而定)。此外,h5和ol也会有简单的回流,因为其在DOM中在回流元素之后。就Opera而言,大部分的回流将导致页面的重新渲染。

Opera原文:Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.

既然它们对性能影响如此可怕,那什么会导致回流呢?

不幸的是,很多的东西,其中一些还与CSS的书写特别相关。

  1. 调整窗口大小(Resizing the window)
  2. 改变字体(Changing the font)
  3. 增加或者移除样式表(Adding or removing a stylesheet)
  4. 内容变化,比如用户在input框中输入文字(Content changes, such as a user typing text in
    an input box)
  5. 激活 CSS 伪类,比如 :hover (IE 中为兄弟结点伪类的激活)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
  6. 操作 class 属性(Manipulating the class attribute)
  7. 脚本操作 DOM(A script manipulating the DOM)
  8. 计算 offsetWidth 和 offsetHeight 属性(Calculating offsetWidth and offsetHeight)
  9. 设置 style 属性的值 (Setting a property of the style attribute)

Mozilla关于回流的文章罗列了导致回流的要点以及何时可以减少他们。

如何避免回流或将它们对性能的影响降到最低?

注意:这里我限定了自己只能讨论CSS对回流的影响,如果您是一位JavaScript程序员,我是推荐您读一下我的reflow链接(zxx: 原作者收藏标记的一些关于reflow的一些文章或页面链接),有一些非常好的东西,没有直接关系到CSS。

  1. 如果想设定元素的样式,通过改变元素的 class 名 (尽可能在 DOM 树的最末端)(Change classes on the element you wish to style (as low in the dom tree as possible))
  2. 避免设置多项内联样式(Avoid setting multiple inline styles)
  3. 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(Apply animations to elements that are position fixed or absolute)
  4. 权衡平滑和速度(Trade smoothness for speed)
  5. 避免使用table布局(Avoid tables for layout)
  6. 避免使用CSS的JavaScript表达式 (仅 IE 浏览器)(Avoid JavaScript expressions in the CSS (IE only))

尽可能在DOM树的最末端改变class

回流可以自上而下,或自下而上的回流的信息传递给周围的节点。回流是不可避免的,但可以减少其影响。尽可能在DOM树的里面改变class,可以限制了回流的范围,使其影响尽可能少的节点。例如,你应该避免通过改变对包装元素类去影响子节点的显示。面向对象的CSS始终尝试获得它们影响的类对象(DOM节点或节点),但在这种情况下,它已尽可能的减少了回流的影响,增加性能优势。

避免设置多层内联样式

我们都知道与DOM交互很慢。我们尝试在一种无形的DOM树片段组进行更改,然后整个改变应用到DOM上时仅导致了一个回流。同样,通过style属性设置样式导致回流。避免设置多级内联样式,因为每个都会造成回流,样式应该合并在一个外部类,这样当该元素的class属性可被操控时仅会产生一个reflow。

动画效果应用到position属性为absolute或fixed的元素上

动画效果应用到position属性为absolute或fixed的元素上,它们不影响其他元素的布局,所它他们只会导致重新绘制,而不是一个完整回流。这样消耗会更低。

牺牲平滑度换取速度

Opera还建议我们牺牲平滑度换取速度,其意思是指您可能想每次1像素移动一个动画,但是如果此动画及随后的回流使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。动画元素每次移动3像素可能在非常快的机器上看起来平滑度低了,但它不会导致CPU在较慢的机器和移动设备中抖动。

避免使用table布局

避免使用table布局。可能您需要其它些避免使用table的理由,在布局完全建立之前,table经常需要多个关口,因为table是个和罕见的可以影响在它们之前已经进入的DOM元素的显示的元素。想象一下,因为表格最后一个单元格的内容过宽而导致纵列大小完全改变。这就是为什么所有的浏览器都逐步地不支持table表格的渲染(感谢Bill Scott提供)。然而有另外一个原因为什么表格布局时很糟糕的主意,根据Mozilla,即使一些小的变化将导致表格(table)中的所有其他节点回流。

Jenny Donnelly, YUI 数据表格 widget的所有者,建议使用数据表格的固定布局以便更有效的布局算法,任何表格-布局的值除了”auto”将引发一个固定布局,根据CSS2.1规范,这将允许表格一行一行的呈递。Quirksmode显示,大部分的浏览器对表格布局属性支持良好。

In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.

固定布局, CSS 2.1 规范

This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.

自动布局, CSS 2.1 规范

避免使用CSS的JavaScript表达式

这项规则较过时,但确实是个好的主意。主要的原因,这些表现是如此昂贵,是因为他们每次重新计算文档,或部分文档、回流。正如我们从所有的很多事情看到的:引发回流,它可以每秒产生成千上万次。当心!

进一步的学习

Yahoo!出色的性能团队做了一个实验,以确定最佳的方法引入外部的样式表文件。我们建议把链接标记放在头部,尽管其比所有其他阻碍进一步渲染的方法慢一秒(6.3至7.3秒)。虽然逐步地渲染不可改变的(用户讨厌盯着一个空白屏幕),但它使我对下载组件和整体响应时间的渲染,重画,回流和CPU的颠簸造成的影响感到好奇。如果我们可以减少加载过程中的回流个数我们可以夺回了失去的时间的十分之一(100毫秒)吗?如果它是多达一半呢?

在SXSW我试图说服史蒂夫的回流是很重要的。我告诉他一个我已经做了很长一段时间的实验,只是没有时间。我希望有人能在我留下来的那个地方继续(提示!提示!)。虽然加载页面我想故意引发不同程度的回流。这或许可以通过切换一个body(实验)标签的class名称来改变body的最后一个child(没有子节点)完成。比较这两者情况,并增加每秒回流的数目,我们可以关联回流反应时间。衡量JS反应对回流的影响将会更难,因为我们所做能够引发回流的事情都可能会影响实验。

最后,量化影响的趣味只有那么一点点,因为浏览器厂商会告诉我们它的问题。或许更有趣的是把注意力集中在回流的原因以及如何避免它们。这将需要更好的工具,所以我需要浏览器厂商和性能社区共同努力,使之成为现实!

事实说话

也许你是一个视觉关注者,这些视频通过很酷的方式将回流过程可视化展示出来。//zxx: youtube视频,想看,需翻墙。

  1. http://www.youtube.com/watch?v=nJtBUHyNBxs
  2. http://www.youtube.com/watch?v=ZTnIxIA5KGw
  3. http://www.youtube.com/watch?v=dndeRnzkJDU

回流消失的疯狂

为了改进性能,浏览器厂商可能限制回流影响临近的节点或者联合其他几个回流形成一次大的改变,就如Mozilla的这篇文章中展示的。这可以提高性能,但有时也可能导致显示问题。您可以使用我们所了解到的关于回流的知识,在必要纠正相关显示问题的时候触发它们。

例如,我们改变图片优化网站上的选项卡,http://smush.it,随着选项卡的不同,内容区域的高度是不断变化的,有时候,下面的投影会被留下,这是因为内容区域上的父节点被切换而其容器可能没有回流,下面这张图是模拟的,因为这个bug很难用相机捕捉,任何试图获取操作都会导致回流而纠正显示。如果你自己发现类似的bug,移动背景图片到下面切换的DOM元素上。

模拟图 >> 张鑫旭-鑫空间-鑫生活

另外一个例子是动态添加列表项,当您将列表项从9个增加到10个或是从99个增加到100个,列表的数字将不会正确的排队显示,这在所有浏览器都是。当总数按数量的顺序增加且浏览器不回流兄弟节点时,队列被打破。快速切换整个列表的显示或是增加添加一个class类,即使没有相关联的样式,也会导致回流和正确的队列显示。

工具

最近有小部分工具产生了一些波澜,Stoyan Stefanov和我已经一直在寻找好的方法来测量回流与重绘,这里有一些(尽管比较早)。要当心,有的在我正确使用之前把浏览器给搞跛了。大部分情况下,你需要每晚安装下最新的版本。

当Mozilla公开MozAfterPaint Firefox API时,互联网界议论纷纷。

  • 更新:Google的Lindsey Simon写了个可以在任意浏览器下测试回流时间的书签工具。非常的赞,注意:所有的震动都是正常的。//zxx: 我测试此链接为500错误
  • John Resig写了个书签工具来可视化绘制事件。
  • Kyle Scholz创建了一个工具用来在页面加载之前可视化绘制事件
  • Yoono的Alex创建了XUL分析工具

是否其他人见到过好的关于回流评估的工具?请发送告知我。

还有其他一对不是直接用来处理回流的工具。

最后,我们需要一个跨浏览器的工具来量化并减少回流和重绘。我希望性能社区能够与浏览器厂商合作,使这一工具成为现实。浏览器厂商已经告诉我们有一段时间了,这是我们未来需要看到的,希望在我们手中。

//正文结束

我补充的点东西

上面展示了三个YouTube的视频,估计是不翻墙看不了,所以呢,我决定补充一个视频,是IE浏览器开发工程师讲解的关于页面性能的视频,从多角度分析,涉及到CSS,JavaScript,http链接,以及回流重绘等关键知识,相信会有所帮助的。如下:


IE浏览器开发工程师为您展示页面性能的优化

翻译水平有限,若是有不准确的地方欢迎提醒。
原创文章,转载请注明来自张鑫旭-鑫空间-鑫生活[http://www.zhangxinxu.com]
本文地址:http://www.zhangxinxu.com/wordpress/?p=600

(本篇完)

分享到:

标签: , , , , , , , ,

赞助商推荐(我也要赞助)

想学到点真东西? ×
如果你有1~3年前端开发经验,不妨 ×


发表评论(目前17 条评论)

  1. ZD-ong说道:

    你好,在回流的触发场景中,如何理解操作CSS属性呢?

  2. Rachel说道:

    IE浏览器开发工程师为您展示页面性能的优化
    这个视频看不了
    能更新一个能看的链接吗 感谢

  3. 翼帆远航说道:

    总结了下,一个元素的回流会引起以下相关元素的回流:
    1. 子元素(例如:该元素的宽高发生改变,内部子元素的布局可能也会发生改变,如子元素浮动)
    2. DOM 中该元素后面的祖先级元素(例如:该元素的宽高发生改变,后面的浮动元素可能会发生位移)
    3. 该元素的父元素甚至祖先元素(例如:该元素的宽高发生改变,其父元素的宽高也会发生改变)

    另外,感谢你分享的 3 个 YouTube 视频,很直观的展示了回流的过程。

  4. 翼帆远航说道:

    回流的过程是怎样的?回流对子元素、兄弟元素、父元素及祖先元素的影响如何?影响条件是什么?

  5. abaddonpoet说道:

    意思是不是造成回流的设置,可能导致页面的渲染出现问题。尽量减少或者替换那9种会造成回流的操作?但是比如第7条,脚本操作dom?这几乎是不可避免的吧?
    看下来感觉就是1个意思:大家多用新api~~而老api的组合得掂量着来~~

  6. echo说道:

    请问为什么 设置style属性也会导致一次reflow?谢谢

  7. 小斯新手说道:

    如果是 css3 的transform :translate 的话,是不是类似relative的移动,也有回流,不仅仅重绘呢?

  8. x269147836说道:

    翻译太晦涩了….

  9. Tim石佛说道:

    好啊,

  10. ck说道:

    讲的真的很好。就是视频资源需要翻墙。呵呵

  11. 潜行者m说道:

    水平还不足以理解到这么高深的程度,还需继续努力.

  12. pr说道:

    做个读后感 :)

    重绘:对某个区域、对象的重新渲染表现

    回流:对某个区域、对象进行重绘,根据条件影响到它的祖先对象进入重绘(并可能无限递归直到顶级祖先对象)

    重绘如何出现:改变对象的形状、坐标、表现以及内容都会引发该对象被重新渲染,这种现象即为重绘。

    回流如何出现:当该对象即将重绘时,浏览器会根据条件判断该对象的重绘结果是否会依赖该对象的祖先元素。如果有则将该对象祖先元素也加入本次重绘。并一直向上寻找,直到条件不匹配。此现象即为回流。

    最后总结:
    1、重绘可能引发回流
    2、回流必定引发重绘
    3、回流条件……暂时未知

  13. 小菜说道:

    不是很理解,迷迷糊糊的

  14. hax说道:

    翻译得比较糟糕。。。

  15. littledu说道:

    这么好的文章怎么没有人回复?不过这些内容高深了。