优化关键渲染路径
网页优化涉及到浏览器输入网址后的每一步,这里取关注相对少的浏览器渲染这一步来看看。
浏览器渲染页面有以下五个步骤:
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
优化关键渲染路径 就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间.
优化关键渲染路径不仅对首屏呈现有重大意义,对于渲染页面帧率,流畅用户体验很关键。
HTML 的简化处理
HTML 在 SPA 盛行的情况下想要简化越来越困难,特别是复杂的大型应用,依赖于一些质量不一的 UI 组件库。随便写几个业务功能,就会生成一大堆的 div 嵌套。
首先,源头能控制当然最好,精简结构,html 语义化。
其次,我们可以将首屏定义的范围再缩小,比如PC、移动端屏幕大小的第一部分。对于 SPA,前端的首屏 html 要么是同构输出,要么是 js 生成。其实这两种情况都是可以定制的,第一时间只输出“首屏” 元素,剩下的内容延迟载入。不过,对于这样的控制比较繁琐和细节,需要针对性优化,哪些部分需要先输出,哪些部分可以延后。
再就是异步和碎片化的概念,或者叫 fiber。像 react 搞 fiber,目前是为了能及时响应用户,将 react 执行纤维化,异步化。我觉得 html 也同样可以做,一个组件一个组件地展现页面,将繁重的浏览器渲染切成一个个小块。也可以说是 PWA(Progressive Web App)。通常我们的选择是输出 loading,等一切就绪后再展示,这往往让我们肆无忌惮地在首页加载一切资源,然后让用户对着 loading 发呆。如果能正确地,分步地展现内容,相信用户能更好地接受这个过程。在 Facebook APP也能看到这种思路的实现,打开 APP 可以先看到灰色的占位符,然后由顶部开始分步加载内容,在这个过程中,仍然可以响应页面上已加载完成的组件的用户操作。
css 的简化处理
CSS 是阻塞渲染的资源。需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。
一般来说,css 都不是首屏的性能瓶颈,但它是高帧率动画的瓶颈。通过 timeline 可以观察 css 样式计算、布局、绘制、合成的性能。
css 的性能问题包括:
- CSS 选择器的嵌套,多层嵌套严重影响 CSS 性能,因为浏览器需要合成CSSOM,进而合成渲染树。
- 文档(HTML, CSS)越大,浏览器需要完成的工作就越多。
- 样式越复杂,绘制需要的时间就越长(例如,单色的绘制开销“较小”,而阴影的计算和渲染开销则要“大得多”)
针对优化的手段:
- 使用 BEM CSS 规范进行编写CSS
- 简化 css, 或异步 css,将不必要的、非当前页的css 按需加载
- 可以尝试将开销大的 css 属性自动提取,打包到另一个文件,延迟加载。比如非常炫的动画、阴影、渐变等。当然只是一个想法,需要进行权衡,一般没有那么大的影响,相比多一个请求代价
display: none;
的应用,浏览器不会渲染带有些属性的 DOM 节点。可以利用它先隐藏不必要的内容,延迟展现
Layout 布局简化与固定
视觉变化时,管道对指定帧的运行通常是:
样式计算、布局、绘制、合成
但这个过程是可以简化的,也是实现高帧率动画的基础。
当不改变元素的几何属性(例如宽度、高度、左侧或顶部位置等),只修改“paint only”属性(例如背景图片、文字颜色或阴影等),即不会影响页面布局的属性,则浏览器会跳过布局,但仍将执行绘制。
如果您更改一个既不要布局也不要绘制的属性,则浏览器将跳到只执行合成:
可以通过 css trigger 来查询 css 属性所触发的对应的渲染过程。
使用一些 3d 属性(比如 translateZ(0)
)可以提升层的独立渲染,一定程度上可以提高绘制性能,但会增加内存和GPU的使用,如无必要,请勿提升元素
综上,如果频繁改变布局,会造成回流和重绘,对 css 的性能是影响最大的,继而造成卡顿。
总结
html, css 的优化不像其他方面的优化,它们和业务结合比较紧密,对业务开发的意识和要求比较高,如果源头没控制好,后面工程构建上的优化也是有限的。
参考:
- 渲染性能 By Paul Lewis
- 关键渲染路径 By Ilya Grigorik