CSS优化笔记–分析的乐趣与收获

译自:http://perfectionkills.com/profiling-css-for-fun-and-profit-optimization-notes/

最近在研究关于单页网页的webapp的性能优化,这是一种大量使用了css3的高动态高互动的应用。这里所说的不仅仅只是圆角和渐变,而是包括阴影、渐变、变形、过渡、平缓的半透明色、聪明的伪元素技巧和实验性的CSS功能。

除了寻找Javascript/DOM侧的瓶颈以外,我还想进入CSS的天地。我想看看这些漂亮的UI对性能有什么样的影响。老版本的app –没有这些小东西 — 却更加迅捷,即使他们背后的js逻辑相同。在滚动页面和播放css动画动画时单页webapp的都会显的很慢。

这是样式的错吗?

幸好,前不久,opera搞出了一个“样式分析器”,它可以展示CSS选择器匹配的性能,页面重绘回流,甚至是页面和CSS的解析时间。

这简直太棒了!
CSS优化笔记–分析的乐趣与收获

对于单一环境的剖析和优化我并不激动,但是我还是决定尝试一下。毕竟,有问题的样式在所有浏览器环境里里都差不多。类似的工具还有WebKit的”timeline”面板,打开开发者工具就可以看到。但是“timeline”这个功能用起来并不是很友好,它没有显示页面重绘/回流/选择器匹配的时间等信息,而提取这些信息只能通过导出json后再手动分析。

笔记

以下是我从opera和WebKit的分析器里得到数据和记录:

1.最快的规则是不存在的。一般样式表是由多个CSS模块构成的,这是一般通用的写法,但这会导致一些问题,比如大的样式表中包含了很多没有在页面中用到的模块。css优化最重要的一个策略,即是剔除没有用到的规则,以减少初次的样式匹配次数。当然,把所有模块集合在一个文件里也有它的好处,比如减少请求数,但是剔除没有用到部分的这种优化是必要的,尤其是在app关键部分,应该只保留有用的样式。

这不是什么新鲜的优化建议,在page speed中有会有这样的提示,但是我更关注的是这样做对渲染时间究竟有多大的影响。在我的测试

中,通过使用opera的分析器对无用CSS进行移除后,选择器匹配时间减少了200~300毫秒。

2.减少回流 — 另一个大家都熟知的css优化 —在这里同样起了很重要的作用。当浏览器执行少量的的回流或者重绘的代码时,本来

不耗时的样式也会变得耗时起来,比如很简单的样式被重复调用了很多次,减少回流减少CSS的复杂性是需要同时处理的问题。

3.最慢的匹配选择符是万能选择符(“*”),还有像类似”.foo.bar”, “foo .bar.baz qux”这样的多级选择器也会很慢,这些都是我们已知的,但是可以通过分析器得到印证。

4.我甚至发现在站点或者app中,只有span标签的按钮也会有类似”button > *”这样的选择符出现。将”button > *”替换为”button > span”后选择器性能有了惊人的提升。浏览器不需要匹配每一个元素,它只需要利用span元素 — 这样可以显著缩小匹

配数量 — 然后检查父级元素是否是button。使用指明的标签代替*号,在优化中是必要的。当然这种优化的一大缺点即是失去了灵活性,当标签改变的时候css也需要做相应调整。为了优化性能而失去了代码的抽象性,这个似乎也是不太可取的,一如往常一样,针对具体情况我们要做出折中的方案,以保证浏览器在选择器匹配时能够高效。

5.通常,我会使用如下的代码去检查哪些”*”型选择器,可以使用某个具体标签来替代

这种做法依赖于Prototype.js中的Array#pluck与Array#uniq方法。对于原生js可能会像如下代码:

6.在opera和webkit中[type="..."]都会比input[type="..."]要耗时。

7.在Opera中,通过分析器发现伪元素”::selection”和”:active”也跻身比较耗时的选择器之列。我可以理解”:active”比较费时,但是对”::selection”却不是很确定,这也许是opera的分析器的bug,也或许是opera内核就是这么处理的。

8.在opera与webkit中,”border-radius”都是最影响渲染时间的CSS属性,甚至比阴影和渐变还要耗时。你可以从这里查看我做的一个测试,在一个页面上创建了400个按钮。

CSS优化笔记–分析的乐趣与收获

我开始检查各条css规则对渲染性能的影响(分析器中是“repaint time”这一项)。基本按钮样式只包含以下css:

CSS优化笔记–分析的乐趣与收获

在opera中这400个具有基本样式的button的重绘时间仅仅只有6毫秒,然后我逐渐增加各种样式,并记录重绘时间的变化。在最终版本里,这些额外的样式使得重绘时间增加大片177毫秒–是原来的30倍。

CSS优化笔记–分析的乐趣与收获

每一个css属性的确切耗时如下图:
CSS优化笔记–分析的乐趣与收获

text-shadow 和 linear-gradient 是最快的属性,Opacity和透明的rgba色次之,下来就是box-shadow了,内嵌的盒阴影(0 1px 1px 0)要快于正常的盒阴影。最耗时的居然是border-radius.

我还尝试改变transform中的rotate参数(仅仅1deg),然后就得到了一组非常高的数据。滚动一个有400个旋转按钮的页面,会明显感觉到页面非常不流畅。在缺乏优化的情况下,基本可以肯定随意transform页面中的元素并不是什么好事,出于好奇,我改变了多种旋转度数,得到了下图:

cialis online
CSS优化笔记–分析的乐趣与收获

我注意到,哪怕是调整0.01度的元素旋转,也会对性能带来巨大的损耗。随着旋转度数的增加,性能似乎在下降,不过这一切似乎不是呈线性增加,而是波浪形(在旋转到45度时性能损耗最高到90度时又迅速下降)

9.在opera中,页面缩放级别会影响到布局性能。减少缩放会增加渲染时间,这真的让人很费解,看起来似乎是个微不足道的细节,但是为了保证测试的连贯性,搞清楚缩放级别没有打搅你的计算非常重要。在发现这个现象后,我重做了所有的测试,以保证没有把桔子当柚子。

10.在opera中缩放浏览器窗口,并不会影响渲染性能。布局以及样式计算在缩放窗口时也并未受到影响。

11.在chrome中缩放窗口则会影响性能,也许是因为chrome比opera更聪明,只会渲染页面的可见部分。

12.在opera中页面重新加载也会影响性能。它呈现明显的线性特征,你可以从下图观察到页面在重新加载40次后渲染时间在缓慢增加。页面绘制时间几乎是开始时候的三倍,看起来就像页面内存泄漏一样。为了慎重起见,我每次都是使用开始到第五次加载的平均数据。

页面重载的脚本:

未监测页面在webkit/chrome中重载对性能的影响。

13.另外一个不太好的模式,是我偶然在SASS中发现的:

将会生成如下的样式:

注意额外的ie7选择器,它拥有一个通用选择符。我们知道在从右到左的匹配中通用选择符会是导致速度变慢的原因之一,所有浏览器除了ie7都认为这会对性能造成不必要的影响。这显然是一个针对IE7写的非常糟糕的选择器。

另外一个稍微好一些的例子:

将会生成这样的例子:

但是即时是这样,浏览器引擎已然要遍历每一个的li元素,直到发现DOM树上没有匹配名为ie7的class.

在我的研究中,最终样式表中有将近上百个形如.ie7和.ie8这样的选择器,很多都使用了通用的子选择符,修复的方法很简单,把ie相

关的样式单独放置在一个独立的样式表中,包括条件注释,这样在请求和匹配时就会分析很少的规则。不幸的是,这种优化方法是有代价的。我发现将IE相关的样式放在主样式下面一行也许是一种最容易维护的解决方案,当样式有任何变动时,至少不会忘记同时修改ie相关的样式。也许在未来像类似SASS这样的工具会自动帮助我们优化主要的样式,而不用人为的为低级浏览器写一些样式。

14.在chrome(webkit内核浏览器中),你可以通过开发者工具的”Timeline”面板来观察页面重绘/回流/样式等css相关的计算性能。Timeline面板允许你将数据另存为json格式,我最初是看到Marcel Duran 这么做的,他使用node.js和脚本来分析和获取数据。

不幸的是,他的布局数据中包含了重新计算样式的时间—这却是我想避免的。我也想避免页面重载。所以我做了个比较简单的处理脚本,过滤掉相关的重绘布局和样式计算,最终汇总每一项的总共耗时。代码如下:

保存timeline面板数据,然后导入到如上的脚本中,经过运算,你会得到这样的结果:

同样,使用前面测试过的400个按钮的测试页,我在timeline面板中也跑了一下,得到数据如下图:

与前面opera中得到的数据类似,border-radius仍然是最耗时的属性, linear-gradient似乎比在opera中更加消耗时间, box-shadow 与text-shaow的差异也似乎更大。

15.当我快结束研究时,WebKit在Prosiles中像opera一样增加了选择器的分析数据![a](https://bug-74004-attachments.webkit.org/attachment.cgi?id=118220) 我对它还不是很熟,但是注意到一个有趣的现象,选择器匹配WebKit要比opera稍快一些。我做的单页app在优化前,选择器匹配这一项

在opera上跑到1,144毫秒,但是在webklit中只有18毫秒!相差65倍。chrome的timeline显示所有的样式计算消耗约37毫秒(大体接近webkit的数据),约52毫秒的重绘时间。

总结

  • 减少选择器数量(包括像.ie7 .foo .bar这样的ie专用的)
  • 避免使用通用的选择器(包括类似[type="url"]这样无限制的属性)
  • 在类似opera这样的浏览器中缩放会影响css性能
  • 在类似chrome这样的浏览器中,窗口缩放会影响css性能
  • 重新加载页面在一些浏览器中可能会影响css性能(如opera)
  • “border-radius” 和 “transform”是最耗时的css属性(至少在webkit与opera)
  • 基于WebKit内核的浏览器的”timeline”面板会为样式重新计算/重绘/回流等时间提供线索
  • WebKit中的选择器匹配速度更快一些

问题

结束这些研究后,我对css性能优化产生了一些新的问题:

  • 使用引号包起来的属性值与没有包裹的在选择器匹配上会差多少(如[type=search] 与 [type="search"])
  • 多个box-shadows/text-shadows/backgrounds属性对性能影响如何,如1个text-shadows vs 3 vs 5
  • 伪类选择器的性能如何
  • border-radius的值的大小对于性能影响究竟多少?是值越大影响越大吗?影响会是随着圆角值增加而线性
    增长吗?
  • !important会影响性能吗?如何影响的?
  • 设备硬件加速对性能的影响几何?
  • 不同组合的样式属性对性能影响如何?(如text-shadow 与 linear-gradient vs. text-shadow 与单色背景)

展望未来

随着我们的页面和app越来越强调高互动性,CSS的复杂性会强,浏览器也越来越愿意支持更多的”高级“css功能,css的性能优化会越来越重要。现在市面上的工具大多只能关注到表面,而我们需要在移动端以及更多浏览器上的分析工具,如IE和firefox,我给Firefox提了一个建议,也许不久的将来我们就能看到这样的工具。我非常希望看到分析css性能的脚本,也许我们可以利用 jsperf.com的网站来展示(或许搞一个专门针对css的更棒),大家可以提供各种css的分析脚本,你说呢?