在了解浏览器的渲染机制前,我们来看看一个非常重要的浏览器内核知识。
浏览器内核
浏览器内核也可以称为渲染进程,核心的部分是“渲染引擎”,主要包括以下线程:
1. GUI渲染线程
-
解析 HTML, CSS 为 DOM Tree和Style Tree, 进行布局和绘制为网页
-
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行
-
注意,GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行。
2. JS 引擎线程
-
如V8 引擎就是 JS 引擎线程,负责 JS 的解析和编译
-
GUI 渲染线程与 JS 引擎线程是互斥的,所以如果 JS 执行的时间过长,就会造成页面的渲染造成阻塞,导致页面渲染卡或时间过长
3. 事件触发线程
当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JavaScript 引擎的处理
4.定时触发器线程
浏览器定时计数器并不是由 JavaScript 引擎计数的, 因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时
5.异步http请求线程
http请求线程
从上面的浏览器内核我们知道了GUI 渲染线程与 JS 引擎线程是互斥的,所以我们应该尽量减少js对dom的直接或频繁操作。
浏览器渲染过程
接着看看浏览器的渲染过程图解1:
GUI渲染线程工作图解2:
从图解中可以看出:
浏览器会解析三个东西:
- 一是HTMLL,浏览器会把HTML结构解析转换DOM树形结构
- 二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像
- 三是Javascript脚本,等到Javascript 脚本文件加载后, 解析js,通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。

阻塞渲染:
阻塞渲染,仅是指浏览器是否需要暂停网页的首次渲染,直至该资源准备就绪
CSS的阻塞渲染
从GUI渲染线程工作图解2中可以看到,Render Tree 需要依赖 DOM Tree和 Style Rules,所以CSS 被视为阻塞渲染的资源,这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕
在外网样式资源下载完成前,页面将会处于白屏现象
如阻塞渲染的样式资源下载超时报错,则会跳过,会使用已经下载完成的CSS资源做解析构建CSSOM
所以在等待一段时间后(资源下载超时后)页面才会显示出来
JS的阻塞渲染
JavaScript 可以查询和修改 DOM 与 CSSOM
所以当 HTML 解析器遇到一个 script 标记时,它会暂停GUI渲染线程构建 DOM,将控制权移交给 JS 选择引擎,等 JS 引擎运行完毕,浏览器会从中断的地方恢复 DOM 构建
解除阻塞
将 JavaScript 脚本显式声明为异步,即可防止其阻塞DOM构建与渲染
向 script 标记添加异步关键字可以指示浏览器在等待脚本可用期间不阻止 DOM 构建
-
defer:异步进行下载,然后等待 HTML 解析完成后(DOM完成构建)按照下载顺序进行执行
-
async:异步进行下载,下载完成后会立即执行,执行时仍然会阻塞
使用preload来声明提前加载css和js资源:
-
因为css和js资源的下载都会阻塞渲染,使用preload来声明提前加载css和js资源
-
preload 提供了一种声明式的命令,让浏览器提前加载指定资源(加载后并不执行),需要执行时再执行
-
css引入标签放在head里面,js引入标签放到最后面;使用preload已经加载好了资源,遇到引入标签了才执行
@vue/cli里面打包就是这样使用的:
<head>
<meta charset="utf-8">
<link href="/vuejs-loadmore/css/app.018dd75a.css" rel="preload" as="style">
<link href="/vuejs-loadmore/js/app.3384a5cf.js" rel="preload" as="script">
<link href="/vuejs-loadmore/js/chunk-vendors.69ad0f2e.js" rel="preload" as="script">
<link href="/vuejs-loadmore/css/app.018dd75a.css" rel="stylesheet">
</head>
<body>
<div id="app"></div>
<script src="/vuejs-loadmore/js/chunk-vendors.69ad0f2e.js"></script>
<script src="/vuejs-loadmore/js/app.3384a5cf.js"></script>
</body>
从浏览器的渲染过程看优化
- html结构不宜嵌套太深
- css尽可能快的加载,首屏可以只加载首屏用到的css,其他css不要一次性加载进来,必要时使用内联引入;css会影响js的解析,css必须放在js前面引入
- js尽可能最小化,不要以为使用了Gzip后js体积得到了极大压缩,但是js解析编译耗时很长,不涉及到当前页面的js被加载进来编译会增加额外耗时
- 使用preload来声明提前加载css和js资源
- 尽量减少js对dom的直接或频繁操作;vue或者react都是操作虚拟dom后再放入页面生成真实dom
浏览器的关键渲染路径
由上面浏览器的渲染过程我们可以总结一下浏览器的关键渲染路径:
- 通过JavaScript操作dom/css,或者用css动画来触发视觉变化
- 有了视觉变化后,浏览器要重新对样式进行计算,计算哪些元素的css收到影响
- 布局Layout/回流Reflow:布局将元素变化的样式绘制到页面上,变化的样式包含width等影响布局的样式,background等不影响页面布局的样式变化不会引起Layout的触发,只会产生Paint
- 绘制Paint:将内容画到页面上,也就是绘制
- 复合图层Composite: 浏览器将不同的内容绘制到不同的层上,最后合成在一起呈现在页面上
布局Layout和绘制Paint
布局和绘制是关键渲染路径中最重要的两步,也是浏览器开销最高的。所以减小布局和绘制的发生,甚至可以避免布局和绘制,就可以很大程度优化性能。
布局Layout也可以称为回流Reflow;布局根据渲染树计算每个节点精确的位置和大小,绘制将内容画到页面上,来看看引起回流重绘的操作分别有哪些:
影响回流重绘的操作
- 添加或删除可见的DOM元素
- display: none
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 修改浏览器大小,字体大小 ...
注意:回流一定会触发重绘,而重绘不一定会回流
浏览器的优化机制
由于每次重排都会造成额外的开销,大多数现代浏览器都会使用队列来批量执行优化重排过程;浏览器会将修改操作放到队列里,直到一段时间或者到了一个阈值,才执行并清空队列。但是,当有获取布局信息的操作的时候,会强制队列刷新,比如访问一下属性或方法:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- getBoundingClientRect
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- 具体可以访问这里 gist.github.com/paulirish/5…
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。
所以我们在修改样式的时候,如果要使用它们,最好将值缓存起来。
减少回流和重绘
下面看看如何减少回流和重绘。
避免触发强制同步布局事件
上面说过,当获取元素的一些布局属性,会导致浏览器强制清空队列,进行强制同步布局。
例如:我们想将一个p标签数组的宽度赋值为一个元素的宽度,我们可能写出这样的代码:
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
这段代码看上去是没有什么问题,可是其实会造成很大的性能问题。在每次循环的时候,都读取了box的一个offsetWidth属性值,然后利用它来更新p标签的width属性。这就导致了每一次循环的时候,浏览器都必须先使上一次循环中的样式更新操作生效,才能响应本次循环的样式读取操作。每一次循环都会强制浏览器刷新队列。我们可以优化为:
const width = box.offsetWidth;
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}
对于复杂动画效果,使用绝对定位让其脱离文档流
对于复杂动画效果,由于会经常的引起回流重绘,因此,我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。
css3硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘。这个时候,css3硬件加速就闪亮登场啦。
css3硬件加速不会触发Layout和Paint,只会触发合成图层Composite Layers.
常见的触发硬件加速的css属性:
- transform
- opacity
- filters
- Will-change
参考链接(Tanks):