什么是快速 CSS? 瓶颈在哪里? 慢选择器和快选择器的规则是否仍然有效? 我们使用的属性是否比选择器更重要? 我觉得是时候重新审视其中一些问题了。
当我们试图去提高网站/Web 应用程序的速度时,在大多数的方案(大局观来看),CSS 优化肯定是优先级较低的。 还有许多其他优化可以提供更轻松和更大的收益。 但是,如果对网站的所有区域(包括 CSS)进行微小的改进,它们将产生更大的差异; 用户将永远受益。
每当交流关于 CSS 的相对“速度”的理论时,其他开发人员经常引用 Steve Souders 从 2009 年开始在 CSS 选择器上的工作。它用于验证诸如“属性选择器很慢”或“伪选择器很慢”之类的说法。
至少在过去的几年里,我一直觉得这类事情根本不值得担心。我多年来一直在推崇一段话是:
With CSS, architecture is outside the braces; performance is inside.
注:不知道这一句怎么翻译好,怪怪的?
但除了参考Nicole Sullivan后来在Performance Calendar上发表的文章来支持我"选择器的使用并不重要的假设“之外,我从未真正测试过这个理论;天赋上的不足和不够完美的分析能力使我甚至无法尝试。
我并没有改变我的想法(假设),但这些天来,我更乐于去尝试这样做来面对大家的嘲笑 — — 如果只是为了让有更多知识/证据的人提供进一步的数据。所以我决定创建一些基本的测试。
测试CSS选择器的速度
上述测试中Steve Souders使用了JavaScript的new Date()。然而,如今,现代浏览器(iOS/Safari除外)支持导航计时API,这为我们提供了更准确的测量方法。我们可以像这样实现它:
<script type="text/javascript">
;(function TimeThisMother() {
window.onload = function(){
setTimeout(function(){
var t = performance.timing;
alert("Speed of selection is: " + (t.loadEventEnd - t.responseEnd) + " milliseconds");
}, 0);
};
})();
</script>
这使我们可以限制所有资产(页面资源)被接收的时间点(responseEnd)和呈现页面的时间点(loaddeventend)之间的时间间隔。
所以,我设置了一个非常简单的测试。20个不同的页面,都有一个相同的巨大DOM,由1000个相同的标记块组成:
<div class="tagDiv wrap1">
<div class="tagDiv layer1" data-div="layer1">
<div class="tagDiv layer2">
<ul class="tagUl">
<li class="tagLi">
<b class="tagB">
<a href="/" class="tagA link" data-select="link">Select</a>
</b>
</li>
</ul>
</div>
</div>
</div>
每个页面的区别仅仅在于应用于选择块中最内部节点的规则。测试了20种不同的选择方法,将最内部的节点染成红色:
- Data attribute
- Data attribute (qualified)
- Data attribute (unqualified but with value)
- Data attribute (qualified with value)
- Multiple data attributes (qualified with values)
- Solo pseudo selector (e.g. :after)
- Combined classes (e.g. class1.class2)
- Multiple classes
- Multiple classes with child selector
- Partial attribute matching (e.g. [class^=“wrap”])
- nth-child selector
- nth-child selector followed by another nth-child selector
- Insanity selection (all selections qualified, every class used e.g. div.wrapper > div.tagDiv > div.tagDiv.layer2 > ul.tagUL > li.tagLi > b.tagB > a.TagA.link)
- Slight insanity selection (e.g. .tagLi .tagB a.TagA.link)
- Universal selector
- Element single
- Element double
- Element treble
- Element treble with pseudo
- Single class
该测试在每个浏览器上运行 5 次,数据是取 5 个结果的平均值。 现代浏览器经过测试:
- Chrome 34.0.1838.2 dev
- Firefox 29.0a2 Aurora
- Opera 19.0.1326.63
- Internet Explorer 9.0.8112.16421
- Android 4.2 (7" tablet)
Internet Explorer 的早期版本(而不是最新的 IE)用于阐明流行浏览器的行为方式,该浏览器无法获得与其他浏览器相同的滚动频繁更新。
想为自己尝试相同的测试吗?从这个 GitHub 链接抓取文件:github.com/benfrain/cs… 。 只需在您选择的浏览器中打开每个页面(记住浏览器必须支持Network Timing API来提醒响应)。还要注意,当我执行测试时,我丢弃了前两个结果,因为它们在某些浏览器中往往异常高。
当考虑到结果时,我认为一种浏览器与另一种浏览器的比较不能够具有实际意义。这不是测试的目的。我们的目的纯粹是为了尝试和评估所使用的不同选择器之间的选择速度的比较差异。因此,在查看表时,向下查看列比查看行更有意义。
以下是测试的结果。所有时间以毫秒为单位:
| Test | Chrome 34 | Firefox 29 | Opera 19 | IE9 | Android 4 |
|---|---|---|---|---|---|
| 1 | 56.8 | 125.4 | 63.6 | 152.6 | 1455.2 |
| 2 | 55.4 | 128.4 | 61.4 | 141 | 1404.6 |
| 3 | 55 | 125.6 | 61.8 | 152.4 | 1363.4 |
| 4 | 54.8 | 129 | 63.2 | 147.4 | 1421.2 |
| 5 | 55.4 | 124.4 | 63.2 | 147.4 | 1411.2 |
| 6 | 60.6 | 138 | 58.4 | 162 | 1500.4 |
| 7 | 51.2 | 126.6 | 56.8 | 147.8 | 1453.8 |
| 8 | 48.8 | 127.4 | 56.2 | 150.2 | 1398.8 |
| 9 | 48.8 | 127.4 | 55.8 | 154.6 | 1348.4 |
| 10 | 52.2 | 129.4 | 58 | 172 | 1420.2 |
| 11 | 49 | 127.4 | 56.6 | 148.4 | 1352 |
| 12 | 50.6 | 127.2 | 58.4 | 146.2 | 1377.6 |
| 13 | 64.6 | 129.2 | 72.4 | 152.8 | 1461.2 |
| 14 | 50.2 | 129.8 | 54.8 | 154.6 | 1381.2 |
| 15 | 50 | 126.2 | 56.8 | 154.8 | 1351.6 |
| 16 | 49.2 | 127.6 | 56 | 149.2 | 1379.2 |
| 17 | 50.4 | 132.4 | 55 | 157.6 | 1386 |
| 18 | 49.2 | 128.8 | 58.6 | 154.2 | 1380.6 |
| 19 | 48.6 | 132.4 | 54.8 | 148.4 | 1349.6 |
| 20 | 50.4 | 128 | 55 | 149.8 | 1393.8 |
| Biggest Diff. | 16 | 13.6 | 17.6 | 31 | 152 |
| Slowest | 13 | 6 | 13 | 10 | 6 |
最快和最慢选择器之间的区别:
“Biggest Diff”列显示了选择器之间的使用最快和最慢的毫秒差异。在桌面浏览器中,IE9在最快和最慢选择器之间的差异最大,为31ms。其它都在这个数字的一半左右。然而,有趣的是,对于使用什么样CSS选择器是最慢的并没有达成共识。
最慢的选择器
我很感兴趣地注意到,最慢的选择器类型因浏览器而异。Opera和Chrome都发现“insanity”选择器(测试13)是最难匹配的(Opera和Chrome在这里的相似性也许并不令人惊讶,因为他们共享的闪烁引擎),而Firefox在单个伪选择器上挣扎(测试6),Android 4.2设备(乐购hudl 7英寸平板电脑)也是如此。Internet Explorer 9的致命弱点是部分属性选择器(测试10)。
我很高兴地注意到,最慢的选择器类型因浏览器而异。Opera和Chrome都发现“insanity”选择器 (test 13) 是最难匹配的 (Opera和Chrome在这里的相似性也许并不令人惊讶,因为他们的内核都是 blink 引擎), 而Firefox在单个伪选择器上挣扎 (test 6), Android 4.2设备(乐购hudl 7英寸平板电脑)也是如此。 Internet Explorer 99的致命弱点部分属性选择器 (test 10).
良好的 CSS 架构(体系)实践
我们可以明确的一点是,使用基于类的选择器的扁平层次结构不仅可以产生更加模块化和更少特定的代码,使其更模块化和可重用,它还提供了与其他选择器一样快的效果(是的,ID选择器可能更快,但我不喜欢依赖ID选择器构建大型代码库)。
对我来说,它印证了我的信念,去担心所使用的选择器类型是什么?这个想法是绝对愚蠢的。对选择器引擎进行二次猜测是没有意义的,因为选择器引擎处理选择器的方式明显不同。而且,最快和最慢的选择器之间的差异并不大,即使是在这么巨大的DOM结构上也是如此。就像我们在英格兰北部说的,“There are bigger fish to fry”。
“There are bigger fish to fry”,这句话的意思应该是我们应该把精力放在页面其他的优化上(能够带来较大的收益),而不要纠结于该使用什么类型的选择器
自从最初写这篇文章以来,WebKit 工程师 Benjamin Poulain 就与他联系,指出他对所使用的方法的担忧。他的评论非常有趣,在他的允许下,下面引用了一些信息:
“通过选择通过加载来衡量性能,你衡量的是比 CSS 大得多的东西,CSS 性能只是加载页面的一小部分: 如果我以 [class^=”wrap”] 的时间配置文件为例(在旧的 WebKit 上拍摄,因此它有点类似于 Chrome),我看到:
约 10% 的时间花在光栅化器上。
约 21% 的时间花在第一个布局(first Layout)上。
约 48% 的时间花在解析器和 DOM 树的创建上
约 8% 用于样式解析
约 5% 用于收集style —— 这是我们应该测试的,也是大部分时间应该花费的。 (剩下的时间分散在很多很多小功能上)。
通过上面的测试,假设我们有一个 100 ms 的基线和最快的选择器。其中,5 ms 将用于收集style。如果第二个选择器慢 3 倍,则总共显示为 110 ms。测试应该报告 300% 的差异,但它只显示 10%。” 在这一点上,我回答说,虽然我理解 Benjamin 指向我们的内容,但我的测试只是为了说明同一页面,在所有其他条件相同的情况下,无论使用的选择器如何,都会呈现大致相同的内容。Benjamin 花时间详细回答: “我完全同意预先优化选择器是没有用的,但原因完全不同:
仅通过检查选择器来预测给定选择器的最终性能影响实际上是不太理想的。在引擎中,选择器被重新排序、拆分、收集和编译。要知道给定选择器的最终性能,您必须知道选择器是在哪个桶中收集的,它是如何编译的,最后 DOM 树是长什么样的?
上面提到的这些东西在各种引擎之间都有差异,使得整个过程更加不可预测。
我反对 Web 开发人员优化选择器的第二个论点是:它们可能会使事情变得更糟。关于选择器的错误信息比跨浏览器的正确信息要多。有人做出正确选择的几率非常低。
在实践中,人们发现 CSS 的性能问题,并开始一一地去删除规则,直到问题消失。我认为这是解决这个问题的正确方法,它很容易,并且会带来正向的结果(正向收益)。”
原因和结果
如果页面上DOM元素的数量减半,如您所料,完成任何测试的速度都会相应下降。(翻译注:这里应该说是DOM数量减半,选择器所处理的时间也会减少吧???),但是在实际中,摆脱DOM这种情况不太现实。这让我想知道 CSS 中未使用样式的数量会对结果产生什么影响。
大量未使用的样式对选择速度有何影响?
Another test:
我从 fiat.co.uk 上买了一份厚厚的(体积大)样式表。大概有3000行CSS。所有这些不相关的样式都插入到最终规则之前,该规则(页面中的内联CSS规则)将选择我们的内部 a.link 节点并将其设为红色。我对在每个浏览器上运行5次,并平均了测试结果。
然后我将这些规则(上方提到的体积大的未使用样式表)减半并重复测试以进行比较。 测试结果如下:
| Test | Chrome 34 | Firefox 29 | Opera 19 | IE9 | Android 4 |
|---|---|---|---|---|---|
| Full bloat | 64.4 | 237.6 | 74.2 | 436.8 | 1714.6 |
| Half bloat | 51.6 | 142.8 | 65.4 | 358.6 | 1412.4 |
Style diet
这里提供了一些有趣的数据。例如,Firefox比它最慢的选择器测试(test6)慢了1.7倍,Android 4.3比它最慢的选择器测试(test6)慢了1.2倍,Internet Explorer比它最慢的选择器测试慢了2.5倍!
您可以看到,当删除了一半的样式(大约 1500 行)后,Firefox 的内容大幅下降。 在这一点上,Android 设备的速度也下降到了其最慢选择器的速度附近。
移除未使用的样式
这种恐怖的场景你听起来是不是很熟悉? 包含各种选择器的巨大 CSS 文件(通常带有甚至不起作用的选择器)、7 层以及更深层次或更特定的(就是比较高级的css选择器规则)选择器块、不适用的前缀、遍布商店(乱七八糟)的 ID 和 50-80KB 的文件大小 (有时更多)。
如果你的代码库有一个像这样的巨大的CSS文件,没有人很确定这些样式实际上是什么 —— 在使用选择器之前,看看你的CSS优化。
首先解决这个问题似乎比去挑剔怎么使用选择器其更加有意义。 它将产生双倍的影响; 用户需要下载的代码更少,UA 解析的代码也更少 —— 这是一个全方位的减速带。
Then again, that won’t help with the actual performance of your CSS. 再说一次,这对CSS的实际性能并没有什么帮助。
Performance inside the brackets
这个标题不知道怎么翻译,括号内的性能???或为CPU/GPU带来负担的性能影响??貌似是一句方言?
我运行的最后一个测试 是在点击页面时,使用一堆“昂贵”的属性和值。
.link {
background-color: red;
border-radius: 5px;
padding: 3px;
box-shadow: 0 5px 5px #000;
-webkit-transform: rotate(10deg);
-moz-transform: rotate(10deg);
-ms-transform: rotate(10deg);
transform: rotate(10deg);
display: block;
}
应用这些规则后,测试结果如下:
| Test | Chrome 34 | Firefox 29 | Opera 19 | IE9 | Android 4 |
|---|---|---|---|---|---|
| 昂贵的样式 | 65.2 | 151.4 | 65.2 | 259.2 | 1923 |
在这里,所有浏览器的测试数据至少都达到了最慢的选择器速度(IE 比其最慢的选择器(test10)慢 1.5 倍,Android 设备比最慢的选择器测试(test6)慢 1.3 倍),但这甚至不是完整的 画像数据。 尝试滚动! 重绘这些 styles 会让你的电脑哭泣。
我们在大括号中插入的属性是真正对系统征税的东西。(The properties we stick inside the braces are what really taxes a system)。按理说,滚动一个页面需要不断地进行重新绘制和布局更改,这将给设备带来压力。好的 HiDPI屏幕?更糟糕的是,CPU/GPU需要在16毫秒内将所有内容重新绘制到屏幕上。
通过这些昂贵的样式测试,在我测试的 15英寸 Retina MacBook Pro上,Chrome 连续绘制模式下的绘制时间从未低于280ms(请记住,我们的目标是低于16ms)。为了更好地理解这一点,第一个选择器测试页面从未超过 2.5 ms。那不是打字错误。这些属性绘制时间增加了112倍。“Holy ’effing expensive properties Batman! Indeed Robin. Indeed.”,该死的昂贵财产,蝙蝠侠!罗宾。确实。
什么属性(css属性)是昂贵的?
我们能够非常确信什么算是“昂贵的”属性/值配对,当浏览器不得不重新绘制屏幕时(例如滚动屏幕),它们会使浏览器陷入困境(为浏览器带来负担)。
我们怎么知道什么是“昂贵的”样式?值得庆幸的是,我们可以运用常识来理解这一点,并很好地了解到什么东西会给浏览器带来负担。任何需要浏览器在绘制到页面之前进行操纵/计算的行为,都会带来负担(昂贵)。例如,box-shadows、border-radius、transparency (浏览器必须计算下面显示的内容)、transforms和性能杀手(如CSS filters) —— 对于你的页面来说,如果性能是比较重要的,那么任何类似的东西(属性)都是您最大的敌人。
早在 2012 年,Juriy “kangax” Zaytsev 就做了 一篇很棒的博客文章,也涵盖了 CSS 性能。他使用的是各种开发人员工具来衡量性能。 他在展示不同的属性对性能的影响方面做得特别好。 如果您对这种事情感兴趣,那么该帖子非常值得您花时间去看。
结论
以下是我从这个小插曲中学到的:
- 担心在现代浏览器中应该去使用什么样的选择器是徒劳的;现在大多数选择器方法都非常快,不值得花太多时间在上面。此外,最慢的选择器在不同的浏览器之间也存在差异。最后看看这里,加快你的CSS。
- 过多未使用的样式,可能比您选择什么样的选择器要花费更多的性能,所以请先清理一下。一个页面上有3000行未使用或多余样式的情况并不罕见。虽然将所有样式集中到一个大的单个
styles.css中是一个很正常的行为,但如果站点/web应用的不同区域可以添加不同的(额外的)样式表(依赖图样式),或许这可能是更好的选择。 - 如果您的CSS是由许多不同的作者(开发者)添加的,那么请使用像UnCSS这样的工具来自动删除未使用的样式 —— 手动完成这个过程一点也不有趣!
- 高性能CSS的胜利并不在于选择器的使用,而是在于对CSS属性和值的合理使用。
- 将内容快速绘制到屏幕上显然很重要,但用户与页面交互时的体验也同样重要。我们应该最优先去寻找昂贵的属性和值对(
Chrome连续重绘模式是你的朋友。 注明:连续重绘模式已经被移除,详见:stackoverflow.com/questions/4… ),它们可能会为你带来最大的收益。
未使用的 CSS 还会减慢浏览器构建渲染树的速度。渲染树类似于 DOM 树,不同之处在于渲染树包括每个节点的样式。要构建渲染树,浏览器必须遍历整个 DOM 树,并检查哪些 CSS 规则适用于每个节点。未使用的 CSS 越多,浏览器就可能需要花费更多的时间来计算每个节点的样式。
资源
原文:CSS performance revisited: selectors, bloat and expensive styles