前言
目前,比较常见的浏览器内核(渲染引擎)有:WebKit、Blink 、Gecko、Trident、EdgeHTML,更多请看。
- WebKit 课代表是 Safari 浏览器
- Blink 课代表是 Chrome 浏览器(Blink 起源自 WebKit 的一个分支)
- Gecko 课代表是 Firefox 浏览器
- Trident 课代表是 IE 浏览器。2015 年微软推出的 Edge 浏览器其内核是 EdgeHTML。从良之后于 2020 年推出基于 Chromium 的 Edge 浏览器,使用 Blink 引擎。
以下是两个主流浏览器内核 WebKit、Gecko 合成 DOM 的过程:
WebKit
Gecko
两者整体流程基本相似,在术语方面也有所不同,比如 WebKit 中的 Layout 过程,Gecko 称为 Reflow。
提高网页性能的其中一个方式就是,减少回流(reflow)、重绘(repaint),分别对应 Layout 和 Painting 过程。
比如,React Hooks 中处理副作用时,某些场景下应选择 useLayoutEffect
,而不是 useEffect
的原因正是为了减少回流重绘的过程。
「回流」也有称作「重排」的。
正文
什么是卡顿?
在很长一段时间里,显示器的刷新率多数为 60Hz,即使到现在仍然是占多数。
刷新率(RefreshRate),表示单位时间内能够绘制新图像的次数。举个例子,60Hz 的刷新率,表示显示器要在一秒内刷新 60 次图像,换句话说,一次图像的更新要在 16.67ms
内完成。这样才不会造成卡顿。如果超出这个时间,在视角上就会产生卡顿感。
60Hz 的刷新率是人类不会感觉到屏幕图像闪烁的数值,由科学家验证得出。
附一个来自网上的图:
还是用 React 举例吧。我们知道 React 16 起采用了全新的 Fiber 架构,就是为了解决大型应用卡顿的问题。
我们知道,浏览器是多进程的,JavaScript 是单线程的。浏览器会为每个标签(Tab)分配一个进程,每个进程由 GUI 渲染线程、JS 线程、定时器线程、网络线程、事件线程多个线程组成。最重要的一点是:GUI 渲染线程与 JS 线程是互斥的。换句话说,某个时刻它只能执行其中一个线程,等待该线程执行完毕之后,才将执行权交由另一个线程。
那么为什么 React 15 在处理大型应用的时候会卡顿呢?
首先 React 15 架构,由协调器(Reconciler)和渲染器(Render)组成。而 React 16 在原来的基础上新增了调度器(Scheduler),用于调度任务的优先级。
在 React 15 的 Reconciler 中,组件的挂载(Mounting)、更新(Updating)会对应调用 mountComponent
、updateComponent
方法,它们内部会执行递归操作,以更新子组件。但是递归执行的缺点是「无法中断」。假设 JS 线程执行递归耗时超过了 16.67ms
,由于互斥期间 GUI 线程并不能执行任何的操作,等递归完并生成新的虚拟 DOM 之后,触发 DOM 等更新,此时由 GUI 线程进行处理,可能包括回流、重绘的过程,然后才完成一次页面的更新。由于无法满足刷新率的要求,就会产生卡顿感。
因此,解决卡顿的思路就是:在每一帧的时间内,预留一些时间给 JS 线程,当预留的时间不够用时,React 中断当前任务,将线程控制权交换给浏览器,使其有时间渲染页面,等下一帧到来的时候,继续被中断的工作。
在 React 16 新增的 Scheduler 可以使得浏览器有剩余时间的时候通知 React,而且提供了多种调度优先级,使得更高优先级的任务优先进入 Reconciler 阶段。 而 Reconciler 则是利用了 Fiber 这种架构实现了「可中断的异步更新」(请注意,这里的异步并不是由 setTimeout
实现的,由于精度问题,setTimeout
实际上最低延迟时间是 4ms
,在这寸土寸金的一帧时间才 16.67ms
,显然是不合理的)。
因此,React 就是采用了这种思路来解决大型应用卡顿问题的。
题外话扯完了,回到本文的主题...
影响页面渲染性能的有什么?
再附上一张源自网上的图:
这几个关键点:
- JavaScript - 使用 JavaScript 脚本来触发 DOM 的更新。
- Style - 匹配各种选择器,并计算出哪些元素应用哪些 CSS 样式的过程。
- Layout - 前面知道一个元素应用哪些规则之后,浏览器开始计算它要占用的空间大小以及在屏幕的位置。一个元素在空间上的变化,会同时影响其他元素的重排。
- Paint - 绘制是填充的像素的过程,比如文本、颜色、图像、边框和阴影等。
- Composite - 合成过程处理元素绘制到哪一层,可能是在某个元素的上层或下层。与层叠上下文有关。
- 如果修改了某个元素的 Layout 属性,那么浏览器会检查其他所有元素,然后“自动重排”,任何受影响的部分都需要重新绘制,然后才进行合成。
- 如果仅修改了「paint only」属性,比如背景图片,文字颜色、背景等,它不会影响页面布局,所以会跳过 Layout 阶段,然后执行重绘。
- 如果修改了一个既不影响 Layout,也无需 Paint 的属性,那么浏览器只执行 Composite 合成过程。这个开销是最小的,可以看下这篇文章。
CSS Triggers
哪些 CSS 属性影响 Layout,哪些影响 Paint 呢?
可以看这个网站 CSS Triggers。
其中 Change from default 表示从未设置(即 CSS 默认值)到设置为其他值,Subsequent updates 表示属性修改。