查看原文
其他

【第291期】复杂应用的 CSS 性能分析和优化建议

2015-07-15 orzpoint 前端早读课

来自早读君:

最近关注这个一发不可收拾,继续前端优化相关内容推荐分享~

正文从这开始~

我最近正在为一个所谓的单页应用做性能优化,这是一个高度异步化、富交互并且使用了很 CSS3 效果的 web app,不单单是用点圆角和渐变之类的,还有阴影、旋转变换、过渡效果、半透明,当然还会用点 CSS 伪类技巧和一些尚在实验阶段的特性。

暂时抛开 Javascript/DOM 性能上的瓶颈,我准备先来研究一下旧版的 CSS,然后搞清楚为什么 UI 这么好性能却很差,虽然旧版的 js 逻辑并没有很大的改动但是它还是让我很火大,只要稍微玩一玩就知道它根本不够流畅。


这是不是样式问题造成的呢?

很巧的是,Opera 最近几天刚好发布了一个样式性能调试器(紧随 WebKit 刚刚的一个关于样式性能分析的 bug 修复),用来展示 CSS 选择器的性能,文档 reflow,repaint 甚至 document 、css 的解析时间。


我以前并不喜欢只在一个环境下调试,当然也不喜欢只针对一个渲染引擎进行分析(特别是只用在一种浏览器里的),不过这次决定试试,毕竟不同的样式规则在不同引擎里是相似的,而且很多是唯一的规则。


唯一一个跟这个工具比较像的是 WebKit 开发者工具中的 Timeline tab,但是没有那么好用,它不显示 reflow/repaint/selector 的时间,唯一的办法是把这些数据倒出来,然后人工分析。


下面就是一些我用 Webkit 和Opera 的工具分析出来的观点。

开始之前,我得提醒一下大家,我的大部分结论最试用于大而且负责的应用,特别是那些有上千个 DOM 节点、极度富交互的的应用。在我自己的项目中,我减少了网面加载时间大约 650ms (500ms 来自的样式重新计算,100ms 来自 repait,50ms 来自 reflow 的时间减少),整个应用变得更快了,特别是在比 IE7 还老的破浏览下。


对于相对简单的页面来说,你先应该去看看其他的一些优化建议。

优化建议

1. 其实并不存在最快的规则,我们通常做法是把样式模块合并到一个文件中试用,这样会导致其中的一部分样式并没有被特定的页面用到。其实把没用的样式规则拿掉是优化 CSS 的最好的方法之一,因为这样的话就可以省去多余的样式匹配,当然合并多个文件到一个大文件还是有好处的,比如说可以减少请求数,但是我们应该只把跟当前页面有关的样式打包到一起。


其实这也不算什么新发现了,Page Speed 早就有过这条建议。不过,我还被它的效果吓到了,去掉多余样式让我节省了大约 200-300ms 的选择器匹配时间(根据Opera 调试工具的结果)。


2. 减少 reflow,这是另外一条总所周知的规则,起了非常大的作用。性能消耗多的样式规则在只有少量 reflow/repaint 的时候并不没有产生那么的性能消耗,但是一条很简单的规则却有可能让这个网页慢起来,所以减少 reflow 必须和减少样式复杂度一起做起来。


3. 性能消耗最大选择器应该是 * 和多 class 选择器(比如 .foo.bar, .foo.bar.baz qux),我们都最大这个,不过最好还是通过分析确定一下


4. 要注意那些本来不需要用的全选符 *,我发现过一个选择器是这样的:button > *,但是我找遍了整个网站,发现所有按钮只有一个 <span> 在里面,所以把 * 换成 span 就可以换来很多的提升,因为浏览器已经不需要去匹配所有的元素了(因为从右向左匹配原则),只需要查找所有的 span,而 span 的数量远比所有元素少,然后再查找父元素是 button 的span 即可,所以应该把 * 替换成其他标签,不过通常比较麻烦。


这种优化的问题是损失了一些可扩展性,因为修改 HTML 之后也需要修改 CSS,你也不能日后再去修改按钮的样式,这样会产生一些无用的规则,所以这条规则我还不怎么确定,还是针对自己的实际情况来做优化吧,除非渲染引擎修改选取规则否则这种优化可以先忽略(译者注:其实通过加 class 而不用 tag 就可以解决这个问题)。


5. 我用这种方法快速找出可以替换成 tag 的* 选择符。


这个方法依赖于 Prototype.js 里的 Array#pluck 和 Array#uniq . 未来有 ES5 和新的选择器或许可以这样:


6. 在 Opera 和 WebKit 中[type=”…”] 比 input[type=”…”] 更加耗时,可能是浏览器认为属性检测对于特定的标签来说才是比较通用方法。


7.跟居 Oprea 的分析工具, ::selection 和:active 是最为耗时的选择器。可理解的是 :active 为什么还是,但是 ::selection 就不知道为什么了,可能是分析工具的bug,或者浏览器本身的渲染方式决定。


8. 在 Opera 和 WebKit 中, border-radius 是最耗时的属性之一,甚至比 shadow 和gradient 和耗时,要注意的是它不影响布局生成时间只影响 repaint 时间。
从下面的测试看到,我创建了有 400个按钮的页面:



我开始查看这么多的样式是怎么影响渲染性能的(分析工具的 repaint time),最简页面的按钮只有这样样式



这些仅仅花了 6ms 就渲染完成(Opera),然后我开始加一些样式,最终的样式如下:(足足花了 177ms 用于 repaint,增加了 30 倍)


每个属性拿出来分析的结果如下:


text-shadow 和 linear-gradient 是最不耗时的. Opacity 和透明 rgba() 样色相对好一些。box-shadow 带这inset one (0 1px 1px 0) 稍微比(0 2px 3px 0) 好一点. border-radius 更高一些。


我也试了transform: ratete(1deg),得到的数据也非常高,稍微滚定一下 400 个按钮的页面还是显得非常不流畅,我觉得不应该轻易使用 transform,然后这只是一个缺少优化的例子?我挺好奇的,所以做了不同角度的测试:


可以发现尽管只有 0.01deg 的转动也是想当耗时,随着度数的增加性能不断下降,但是并不是线性的增长,到了 45deg 有一个回落直到 90deg。

这里还有很多测试的空间,我很想看到transform 的其他属性在图同浏览器中表现如何。


9. 在 Opera 中,网页缩放比例影响布局性能,缩小比例反而越耗时,这很好理解,毕竟同一个地方现在要渲染更多的东西,不过为了让实验能保持一直,要确保所有的测试是在一个缩放比例中进行的,我发现我得重新在不同的缩放比例中做上面的这些式样。

说到缩放,测试一下缩小字体会不会影响整体的性能可能会有用。


10. 在Opera 中,缩放窗口大小不会影响渲染,看起来布局,渲染和样式的计算和窗口大小没上面关系。


11. 在 Chrome 中却相反。


12. 在 Opera 里,刷新页面会导致性能下降,并且呈持续下降趋势,从下面的图可以看出在刷新 40 个页面之后渲染时间不断地变慢(底部的红色方块对应每次页面加载时间,其中有一小段的刷新时间间隔),到最后 Paint 时间几乎是第一次的 3 倍,看起来像是每个页面都泄漏了些东西出来,处于谨慎,我总是去前 5 次平均来计算一个正常的数值。


用来测试刷新页面的脚本:


我还没在 WebKit/Chrome 中测试上面的情况。


13. 我遇到过一个想当恶心的情况是这样,在 SASS 语法中:


它会编译成这样:


注意那个 IE7 选择器,为什么会带上通配符,我们知道通配符是很慢的,所以在除了 IE7 之后的所有浏览器(其实就是在 <body> 上加了 .ie7 class 的)中这将会导致额外的性能消耗,这个很明显是用得最差的 IE7 专用选择器

这还有一个类似的:


会编译成:


这里例子里,渲染引擎还是需要去找每个 <li>(在 .steps 下的),直到它发现在 dom 树网上并没有任何 class 是ie7 的元素。


我这里的情况是,我在最终编译完的 css 中用了非常多的跟 ie7 ie8 这样的选择器,很多还是通配符。有个很简单的办法,就是把所有 IE 相关的样式移到一个单独的文件,然后有条件注释加载,然后就不会有那么多的选择器需要去解析,匹配,渲染。


不过,这种优化是有代价的,我发现把 IE 相关的样式归到单独文件维护起来确实是一个问题,到了要修改、增加、删除一些样式的时候,都要去那个文件里修改,或许将来 SASS 这样的工具能够为这样的情况做一些优化,把这些单独做到一个用条件注释加载的文件中。


14. 在 Chrome 中(包括 WebKit 内核的其他浏览器),你可以用开发者工具中 Timeline 栏看到 repaint/reflow 和样式计算性能,并且支持导出 JSON 格式的数据,我第一次看到这样功能是在今年的 PerformanceCalendar 上,由Marcel Duran 完成的,他用 node.js 和一个简本来解析和提取数据。


不过,他的脚本包含了我不想要的”重新计算样式“的时间,我也不想要页面刷新的数据,所以我抽取了一个简单版本,他会分析所有数据,过滤出跟 repaint,布局和样式重算的数据,然后把这些数据加起来。

Layout 6.64 ms Recalculate Styles 0.00 ms Paint 114.69 ms Total 121.33 ms

在拿到 timeline 数据并且跑了过滤脚本之后,你可以拿到数据:

Layout 6.64 msRecalculate Styles 0.00 ms Paint 114.69 ms Total 121.33 ms

用 Chrome 的Timeline 和这个脚本,我试了上面的那个在 Opera 中做的按钮测试:


跟 Opera 差不多, border-radius 似乎是性能最差的,不过 linear-gradient 比在 Opera 中性能消耗多得很多, box-shadow 的性能消耗也比 text-shadow 多很多。


值得一提的是,Timeline 只提供了 Layout 的信息,然而 Opera 的还有 Reflow,我不确定reflow的数据是包含在 layout 数据里还是被忽略,为了更准确的测试,以后会去搞搞清楚。


15. 在我快做完这些测试的时候,WebKit 已经加了一个跟Opera 类似的选择器性能分析工具。


我没办法做那么多测试,不过有件事得说一下,在 WebKit 中选择器的匹配有少部分比在Opera 中的快,同样的一个 HTML(就是那个优化之前的单页应用)在 Opera 中花了 1144ms 在选择器上,但是在 WebKit 中只有 18ms,差了 65倍,要嘛是有的东西没有被算到最终数据中,要嘛是 WebKit 真的在选择其匹配上快非常非常多。Chrome 的 timeline 显示,样式计算花了大约 37ms,repaint 用了大约 52ms(opera 中花了 225ms,虽然不一样,但是挺接近的),在 WebKit 中保存不了 Timeline,所以 reflow 和repaint 的数据也就看不到了。


总结

1)减少选择器的数量(包括这样的跟浏览器有关的: .ie7 .foo .bar)

2)不要用通配符(包括想这样不规范的类型选择符[type="url"])

3)页面的缩放比例是会影响 CSS 性能的(比如: Opera)

4)窗口大小也会影响 CSS 性能(比如: Chrome)

5)页面反复重新加载会降低 CSS 性能(比如 Opera)

6)“border-radius” 和 “transform” 是性能最差的 CSS 属性(至少在WebKit & Opera 中是)

7)基于 WebKit 的浏览器中,Timeline 栏可以算出总的样式计算/reflow/repaint 时间

8)WebKit 中的选择器匹配快得多很多


长按图片识别图中二维码


推荐前端公众号:

1)w3ctech

2)跨界码农 HiNotes



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存