在讨论性能优化之前我们有必偠了解一些浏览器的渲染原理。不同的浏览器进行渲染有着不同的实现方式但是大体流程都是差不多的,我们通过 Chrome 浏览器来大致了解一丅这个渲染流程
关键渲染路径是指浏览器将 HTML、CSS 和 JavaScript 转换成实际运作的网站必须采取的一系列步骤,通过渲染流程图我们可以大致概括如下:
- 绘制页面需要先构建 Render Layer Tree 以便用正确的顺序展示页面这棵树的生成与 Render Object Tree 的构建同步进行。然后还要构建 Graphics Layer Tree 来避免不必要的绘制和使用硬件加速渲染最终才能在屏幕上展示页面。
DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML 交互的 API 文档DOM 是载入到浏览器中的文档模型,它用节點树的形式来表现文档每个节点代表文档的构成部分。
需要说明的是 DOM 只是构建了文档标记的属性和关系并没有说明元素需要呈现的样式,这需要 CSSOM 来处理
获取到 HTML 字节数据后,会通过以下流程构建 DOM Tree:
- 编码:HTML 原始字节数据转换为文件指定编码的字符串
- 词法分析(标记化):对输入字符串进行逐字扫描,根据 识别单词和符号分割成一个个我们可以理解的词汇(学名叫 Token )的过程。
- 语法分析(解析器):对 Tokens 应鼡 HTML 的语法规则进行配对标记、确立节点关系和绑定属性等操作,从而构建 DOM Tree 的过程
词法分析和语法分析在每次处理 HTML 字符串时都会执行这個过程,比如使用 /bootstrap/plex {
因此如果可能,还是应该使用开销更小的样式替代当前样式实现最终效果
首先我们先来了解一下什么是重排和重绘。
-
重排是指因为修改 style 或调整 DOM 结构重新构建部分或全部 Render Object Tree 从而计算布局的过程这一过程至少会触发一次,既页面初始化
-
重绘是指重新绘制受影响的部分到屏幕。
观察像素通道会发现重绘不一定会触发重排比如改变某个节点的背景色,只会重新绘制这个节点而不会发生重排,这是因为布局信息没有发生变化;但是重排是一定会触发重绘的
下面的情况会导致重排或者重绘:
重排和重绘会不断触发,这是不可避免的但是,它们非常消耗资源是导致网页性能低下的根本原因。
提高网页性能就是要降低重排和重绘的频率和成本,尽可能少的触发重新渲染
浏览器面对集中的 DOM 操作时会有一个优化策略:创建一个变化的队列,然后一次執行最终只渲染一次。
上面的代码在浏览器优化后只会执行一次渲染但是,如果代码写得不好变化的队列就会立即刷新并进行渲染;这通常是在修改 DOM 之后,立即获取样式信息的时候下面的样式信息会触发重新渲染:
-
多利用浏览器优化策略。相同的 DOM 操作(读或写)應该放在一起。不要在读操作中间插入写操作
-
不要频繁计算样式。如果某个样式是通过重排得到的那么最好缓存结果。避免下一次使鼡的时候再进行重排。
-
不要逐条改变样式通过改变 className
或 cssText
属性,一次性改变样式
-
使用离线 DOM。离线意味着不对真实的节点进行操作可以通过以下方式实现:
- 使用
cloneNode
方法,在克隆的节点上进行操作然后再用克隆的节点替换原始节点
- 将节点设为
display: none;
(需要一次重排),然后对这个節点进行多次操作最后恢复显示(需要一次重排)。这样一来就用两次重排,避免了更多次的重新渲染
- 将节点设为
visibility: hidden;
和设为 display: none;
是类似的,但是这个属性只对重绘有优化对重排是没有效果的,因为它只是隐藏但是节点还在文档流中的。
- 设置
position: absolute | fixed;
节点会脱离文档流,这时因為不用考虑这个节点对其他节点的影响所以重排的开销会比较小。
- 使用 flexbox 布局flexbox 布局的性能要比传统的布局模型高得多,下面是对 1000 个
div
节点應用 float
或 flex
布局的开销对比可以发现,对于相同数量的元素和相同视觉的外观flex
布局的开销要小得多(float 37.92 ms |
终于,我们到了像素管道的末尾对於这一部分的优化策略,我们可以从为什么需要 Composited Layer(Graphics Layer)来入手这个问题我们在构建 Graphics Layer Tree 的时候,已经说明过现在简单回顾一下:
- 利用硬件加速高效实现某些 UI 特性。
根据 Composited Layer 的这两个特点可以总结出以下几点优化措施。
上文我们说过像素管道的 Layout 和 Paint 部分是可以略过只进行 Composite 的。实现這种渲染方式的方法很简单就是使用只会触发 Composite 的 CSS 属性;目前,满足这个条件的 CSS 属性只有 transform
和 opacity
。
如果不能避免绘制我们就应该尽可能减尐需要重绘的区域。例如页面顶部有一块固定区域,当页面某个其他区域需要重绘的时候很可能整块屏幕都要重绘,这时固定区域吔会被波及到。像这种情况我们就可以把需要重绘或者受到影响的区域提升为 Composited Layer,避免不必要的绘制
对于不支持的浏览器,最简单的 hack 方法莫过于使用 3D 变形来提升为 Composited Layer 了。
根据上文所讲的例子我们尝试使用 will-change
属性来让固定区域避免重绘。
首先我们来看下没有经过优化的情況;顺带说明查看浏览器一帧绘制详情的过程。
- 点击设置(标记 1)开启绘制分析仪(标记 2)。
- 启动 Record(标记 3)获取到想要的信息后,点擊 Stop(标记 4) 停止 Record。
- 点击这一帧的 Paint(标记 5)查看绘制详情
- 切换到 Paint Profiler 选项卡(标记 6),查看绘制的步骤
通过上面的图片(标记 7 和标记 8)可鉯看到,固定区域的确被波及到并且触发重绘了。我们再对比使用 will-change
属性优化过的情况发现固定区域没有触发重绘。
并且我们也可以通过一帧(标记 1)的布局详情(标记 2),查看固定区域(标记 3)是不是提升成 Composited Layer(标记 4)才避免的不必要绘制。
提升成 Composited Layer 的确会优化性能;泹是要知道创建一个新的 Composited Layer 必须要额外的内存和管理,这是非常昂贵的代价所以,在内存资源有限的设备上Composited Layer 带来的性能提升,很可能遠远抵不上创建多个 Composited Layer 的代价同时,由于每一个 Composited Layer 的位图都需要上传到
GPU;所以不免需要考虑 CPU 和 GPU 之间的带宽以及用多大内存处理 GPU 纹理的问题。
我们通过 1000 个 div
节点来对比普通图层与提升成 Composited Layer 之后的内存使用情况。可以发现差距还是比较明显的
通过上文的说明,我们知道 Composited Layer 并不是越哆越好尤其是,千万不要通过下面的代码提升页面的所有元素这样的资源消耗将是异常恐怖的。
最小化提升就是要尽量降低页面 Composited Layer 的數量。为了做到这一点我们可以不把像 will-change
这样能够提升节点为 Composited Layer 的属性写在默认状态中。至于这样做的原因我会在下面讲解。
看这个例子我们先把 will-change
属性写在默认状态里;然后,再对比去掉这个属性后渲染的情况
我们发现区别仅在于,动画的开始和结束会触发重绘;而動画运行的时候,删除或使用 will-change
是没有任何分别的
这条理由赐予了我们动态提升 Composited Layer 的权利;因此我们应该多利用这一点,来减少不必要的 Composited Layer 的數量
我们在 Graphics Layer Tree 中介绍过层爆炸,它指的是由于重叠而导致的大量额外 Composited Layer 的问题浏览器的层压缩可以在很大程度上解决这个问题,但是有佷多特殊的情况,会导致 Composited Layer 无法被压缩;这就很可能产生一些不在我们预期中的 Composited Layer也就是说还是会出现大量额外的 Composited
在层压缩这一节,我们已經给出了使用层压缩优化的例子这里就不再重复了。下面再通过解决一个无法被层压缩的例子来更为深入的了解如何防止层爆炸。
这個例子虽然表面上看起来没有发生重叠;但是因为在运行动画的时候,很可能与其他元素造成重叠所以 .animating
元素会假设兄弟元素在一个 Composited Layer 之仩。这时又因为 .box
元素设置了 overflow: hidden;
导致自己与
解决这个问题的办法也很简单,就是让 .animating
元素的 z-index
比其他兄弟元素高因为 Composited Layer 在普通元素之上,所以也僦没有必要提升普通元素修正渲染顺序了。这里我在顺便多说一句默认情况下 Composited Layer 渲染顺序的优先级是比普通元素高的;但是在普通元素設置
本文首先讲了渲染需要构建的一些树,然后通过这些树与像管道各部分的紧密联系整理了一些优化措施。例如我们对合成所进行嘚优化措施,就是通过 Graphics Layer Tree 来入手的
优化也不能盲目去做,例如提升普通图层为 Composite Layer 来说,使用不当反而会造成非常严重的内存消耗。应当善加利用 Google 浏览器的调试控制台帮助我们更加详尽的了解网页各方面的情况;从而有针对性的优化网页。
文章参考了很多资料这些资料嘟在每一节的末尾给出。它们具有非常大的价值有一些细节,本文可能并没有整理可以通过查看它们来更为深入的了解。