# 页面渲染性能优化
# 浏览器渲染过程(Webkit)
在渲染方面我们要减少重排和重绘,因为他们会影响浏览器性能。可是,为什么,原理是什么呢?
浏览器的解释器,是包括在渲染引擎内的,我们常说的 Chrome(现在使用的是 Blink 引擎)和Safari 使用的Webkit 引擎, Firefox 使用的 Gecko 引擎,指的就是渲染引擎。而在渲染引擎内,还包括着我们的 HTML 解释器(渲染时用于构造 DOM 树)、 CSS 解释器(渲染时用于合成 CSS 规则)还有我们的 JS 解释器。不过后来,由于 JS 的使用越来越重要,工作越来越繁杂,所以 JS 解释器也渐渐独立出来,成为了单独的 JS 引擎,就像众所周知的 V8 引擎,我们经常接触的 Node.js 也是用的它。
# DOM 渲染层与 GPU 硬件加速
一个页面是有许多许多层级组成的,他们就像千层面那样。
页面的真实样子就是这样,是由多个 DOM 元素渲染层(Layers)组成的,实际上一个页面在构建完 render tree 之后,是经历了这样的流程才最终呈现在我们面前的。
- 浏览器会先获取 DOM 树并依据样式将其分割成多个独立的渲染层。
- CPU 将每个层绘制进绘图中。
- 将位图作为纹理上传至 GPU(显卡)绘制。
- GPU 将所有的渲染层缓存(如果下次上传的渲染层没有发生变化, GPU 就不需要对其进行重绘)并复合多个渲染层最终形成我们的图像。
从上面的步骤我们可以知道,布局是由 CPU 处理的,而绘制则是由 GPU 完成的。
# 重排与重绘
现在到我们的重头戏了,重排和重绘。
- 重排(reflow):渲染层内的元素布局发生修改,都会导致页面重新排列,比如窗口的尺寸
发生变化、删除或添加 DOM 元素,修改了影响元素盒子大小的 CSS 属性(诸如: width、 height、
padding)。 - 重绘(repaint):绘制,即渲染上色,所有对元素的视觉表现属性的修改,都会引发重绘。
我们习惯使用 chrome devtools 中的 performance 版块来测量页面重排重绘所占据的时间
- 蓝色部分: HTML 解析和网络通信占用的时间
- 黄色部分: JavaScript 语句执行所占用时间
- 紫色部分:重排占用时间
- 绿色部分:重绘占用时间
不论是重排还是重绘,都会阻塞浏览器。要提高网页性能,就要降低重排和重绘的频率和成本,近可能少地触发重新渲染。
重排是由 CPU 处理的,而重绘是由 GPU 处理的,CPU 的处理效率远不及 GPU,并且重排一定会引发重绘,而重绘不一定会引发重排。所以在性能优化工作中,我们更应当着重减少重排的发生。
# 优化策略
- CSS 属性读写分离:浏览器没次对元素样式进行读操作时,都必须进行一次重新渲染(重排 +重绘),所以我们在使用 JS 对元素样式进行读写操作时,最好将两者分离开,先读后写,避免出现两者交叉使用的情况。最最最客观的解决方案,就是不用 JS 去操作元素样式,这也是我最推荐的。
- 通过切换 class 或者 style.csstext 属性去批量操作元素样式
- DOM 元素离线更新:当对 DOM 进行相关操作时,例、 appendChild 等都可以使用 Document
Fragment 对象进行离线操作,带元素“组装”完成后再一次插入页面,或者使用 display:none 对元素隐藏,
在元素“消失”后进行相关操作 - 将没用的元素设为不可见: visibility: hidden,这样可以减小重绘的压力,必要的时候再将元素显示
- 压缩 DOM 的深度,一个渲染层内不要有过深的子元素,少用 DOM 完成页面样式,多使用伪
元素或者 box-shadow 取代 - 图片在渲染前指定大小:因为 img 元素是内联元素,所以在加载图片后会改变宽高,严重的情况会导致整个页面重排,所以最好在渲染前就指定其大小,或者让其脱离文档流
- 对页面中可能发生大量重排重绘的元素单独触发渲染层,使用 GPU 分担 CPU 压力。(这项策略需要慎用,得着重考量以牺牲 GPU 占用率能否换来可期的性能优化,毕竟页面中存在太多的渲染层对与 GPU 而言也是一种不必要的压力,通常情况下,我们会对动画元素采取硬件加速。)