使用 Chrome 开发工具优化 Web 应用
本文主要介绍如何使用 Chrome 开发工具的 Timeline 功能来监控 Web 应用的性能,找出问题,并由此延伸出一些优化 Web 应用性能的思路。
当我们写了一组炫 (fu) 酷 (za) 的 H5 动效后,发现在移动端上会卡顿,该怎么办? 目前市场上大多数设备的屏幕刷新频率是 60Hz,想要用户滑动页面或者播放动画没有卡顿,浏览器的渲染速率就要和屏幕刷新的频率同步,达到 60fps。
Chrome 开发工具的 Timeline 面板监控了 web 应用运行时所有活动情况,不过它的功能很多,对于英文不好的童鞋,有点无从下手,下面直接上手来使用。
首先,审查一个页面,切换到 Timeline 面板。
图中红框标出的部分是功能栏:从 45 到 47 版本,Timeline 工具连续都有更新,当前截图的版本号是 47.0;
- 开始/停止记录,打开 Timeline 面板时刷新页面会自动开始记录
- 清除已有记录
- 过滤内容
- 强制垃圾回收
- 视图模式,最新版本中点击切换两种视图模式
- 捕获内容选项,选中最后一项截屏的话会保留每帧的截图,记录数据的 buffer 空间会和快被占满
接下来记录一段时间线状态。
图中没被标出的部分是整个监控过程中的数据概览;红框标出的部分有两栏,上面是选中的时间段内每一帧的情况,下面是内存占用的变化。
浏览器渲染的速率达到 60 帧/秒,那么每一帧只有 1000ms / 60 = 16.67ms 的时间来响应,其中浏览器在每一帧还要做一些额外的事情,因为我们要保证每一帧的 CPU time 在 12ms 左右。
有红色三角形角标标出的表示当前帧消耗过多时间
点击选中一帧可以看到当前帧的详细情况,图中显示,当前帧的渲染消耗了 30.8ms,可能会造成卡顿。 饼图中可能会有五个颜色:
- 蓝色: 加载
- 黄色: 脚本执行
- 紫色: 渲染
- 绿色: 绘制
- 灰色: DevTools 不感知的活动
图中帧因为没有加载活动,没有蓝色区域。可以看出脚本执行和帧绘制占据了大部分面积,不过问题并不在这里,因为它们总共才花费 7.82s。 在 Chrome 的 45.0 版本中,饼图里还有一种颜色:白色,表示刷新周期里空闲的时间。在这段时间里,一直在等待用户的响应,直到一个交互事件触发了页面视图的变化。最新的更新里在饼图中去掉了白色,区分为 CPU time 和帧的 Duration,更加合理。这里引起视图渲染和绘制可能有:
- 用户的交互行为
- 定时器触发
使用 requestAnimationFrame 这个函数,将脚本执行和渲染流程联系起来,以避免在某一帧的中间突然执行脚本导致重新渲染和绘制而整个过程不能在 16ms 内完成。
分析一个问题帧
这一帧比较极端,从图上看就是紫色部分花费掉了大量时间,我们就可以从这里入手来优化代码,在此之前先得了解一下浏览器绘制帧的整个过程。实际导致绘制帧卡顿可能是其中任何一个环节。
- Javascript: 除了使用 Javascrit 来实现视觉变化,还有 CSS Animations、 Transitions。
- Style: 根据 CSS 选择器,对每个 DOM 元素匹配对应的 CSS 样式。
- Layout: 在上一步确定了每个 DOM 元素的样式规则后,这一步就是具体计算每个 DOM 元素最终在屏幕上显示的大小和位置。web 页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。例如父元素的宽度变化引发子元素宽度变化,又联动的引发孙子元素的宽度变化。
- Painting: 绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个 DOM 元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。
- Composite: 上一步的绘制是在多个层上进行的,在这一步,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。
说明,上述过程的解释总结或直接引用自谷歌开发者文档。
在以上 5 个过程里:
- 修改 DOM 元素的“layout”(宽、高、位置) 属性,浏览器会检查哪些元素需要重新布局,然后对页面激发一个 reflow 过程完成重新布局。被 reflow 的元素,接下来也会激发绘制过程,最后激发渲染层合并过程,生成最后的画面。
- DOM 元素有一些“paint only”属性,例如背景图片、文字颜色、阴影,这些属性不会影响页面的布局,因此浏览器会在完成样式计算之后,跳过布局过程,只做绘制和渲染层合并过程。
- 还有一些 CSS 属性既不需要重新布局也不需要重新绘制,例如,transform、opacity(目前,只有这两个属性),浏览器会在完成样式计算之后,直接做渲染层合并。
- 用 translateZ(0) 属性把动画元素提升到单独的渲染层中(我们常说的启用 3D 加速)。
对于滚屏和动画,最理想的就是只使用 transform 和 opacity 来实现视觉变化效果,并且遵循第四点。
点击切换视图模式,找到问题所在。
这是一个知名线上网站的首页,这里发生了滚屏触发加载内容,导致 DOM 结构变化,引发了大量内容的重绘和渲染,并且页面旧的内容没有回收,节点较多,明显的感觉到滚屏的时候有卡顿感。对于这个应用按照上述的思路来优化,在优化的过程中,我们也许会遇到下面这个问题。
这是另一个 demo 页面,有数千个节点组成,用以模拟无限懒加载数据的情况,其中只有一个节点上应用了循环的 css3 动画。其它的都不会改变。为了将有动画的节点独立到一个单独的渲染层中,我对它应用了 translateZ(0)。本以为大功告成,结果在滚屏的时候发现非常卡顿。于是就有了上图,平均每帧的渲染都消耗掉了 200ms 左右,从 Timeline 监控数据中可以看到,其中 Composite Layers 这个过程耗费了大量时间。那么直接查看 layer 状态:
再看官方文档的提示:由于每个渲染层的纹理都需要上传到 GPU 处理,过多的渲染层来带的开销而对页面渲染性能产生的影响,甚至远远超过了它在性能改善上带来的好处。
实际上页面里并没有主动通过 translateZ 属性来独立更多的层,我们可以认为这是触发了浏览器的一个“bug”,最新版本的 chrome 仍然会触发这个问题,不过最新版本的 ios 里 Safari 已经不会触发这个问题了。至于什么情况会触发浏览器去创建一个独立层来渲染元素,例如拥有 3DCSS 属性的元素、使用加速视频解码的元素等,对这个问题这里就不再详述。
如果手动的将大量的层独立出来渲染,导致 GPU 罢工,或是任由他们导致 CPU 罢工,都会造成性能问题,不过有了 Timeline 工具,我们就能找到问题所在,一一去分析解决了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论