浏览器渲染过程(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 而言也是一种不必要的压力,通常情况下,我们会对动画元素采取硬件加速。)