浏览器页面渲染机制
# 0.前言
本篇博客主要针对的是Web性能优化的基础理论知识,HTML资源是以何种方式何种顺序被加载到浏览器中的。
# 1.HTML的解析过程
假设现在有一个index.html资源:
<head>
<link rel="stylesheet" type="text/css" href="./*.css" />
</head>
<body>
<h1>你好</h1>
<!-- 加载外部资源 -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>
<!-- 边下载边解析 -->
<script type="text/javascript">
document.write("Hello World!")
</script>
<div>更多内容...</div>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
HTML 解析过程如下:
边下载边解析
HTML,以下均是HTML的解析过程:解析到:
<link rel="stylesheet" type="text/css" href="./*.css" />于是开始去边下载边解析
css资源css的解析和下载并不会阻塞
HTML的解析过程。解析到:
<h1>你好</h1>,开始构建DOM tree解析到:
<script src="https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js"></script>- 开始下载
cdn资源,下载后执行javascript。
🚨注意:此时
javascript的下载和执行会阻塞HTML的解析- 开始下载
解析到:
<div>更多内容...</div>,此时解析结束。
# 2.HTML解析被JS阻塞的原因
以上HTML的解析过程会被两个行为阻塞:
JS的下载过程:在早期浏览器中并不会处理此类阻塞,原因是:浏览器偷懒了,性价比不高。整个
HTML解析过程可能只有1ms,而下载过程需要100ms,对性能的提升不大。JS的执行过程:原因是可能执行过程中遇到以下代码:
<script type="text/javascript"> document.write("Hello World!") </script>1
2
3即,
JS的操作可能会修改DOM树的构建,如果HTML不能JS执行结束,就直接解析出DOM树,会存在DOM树构建失败。
# 3.async与defer的区别
通过设置async和defer属性可以将JS的下载,独立出HTML的解析,并且对JS的执行顺序也可以控制。
对于script可以设置如下标签:
<script></script>
<script defer></script>
<script async></script>
<script type="module"></script>
<script type="module" async></script>
2
3
4
5
下图为不同script属性值下的执行流程图:
其中,绿线代表
HTML解析过程,蓝线代表JS下载,红线代表JS执行
从上图中可以得知:async和defer作用都是不让JS下载阻塞HTML执行。
两者的区别在于:下载后JS的执行顺序
async:这个属性只有一个原则,即一旦下载结束就立即执行js文件。上图中存在一个问题,看上去
async会阻塞HTML解析,但是实际上fetch的下载要远远长于HTML的解析流程。defer:推荐使用,因为async并不能保证js代码的执行顺序,执行顺序取决于网络下载情况,此时JS执行顺序会比较松散,而defer的执行顺序,取决于定义代码的位置。并且在HTML解析后执行。module:过程基本等同于defer,衍生出的其余蓝线是包依赖的modules。module+async:依赖结束后,直接执行。
# 4.如何区分prefetch与preload
这两个标签的作用是:让浏览器提前加载指定资源(与defer等字段不同的是:加载后并不执行),在需要执行的时候再执行。优点是:
- 将加载和执行分离开,不会阻塞
HTML渲染和document的onload事件。 - 提前加载指定资源,不再出现依赖的
font字体隔了一段时间才刷出。
使用方式:
<!-- 使用 link 标签静态标记需要预加载的资源 -->
<link rel="preload" href="/path/to/style.css" as="style">
<!-- 或使用脚本动态创建一个 link 标签后插入到 head 头部 -->
<script>
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'style';
link.href = '/path/to/style.css';
document.head.appendChild(link);
</script>
2
3
4
5
6
7
8
9
10
11
preload 和 prefetch的应用场景:
preload是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源。prefetch是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源。如,某一场景下,页面加载后会初始化首屏组件,当用户滚动页面时,会拉取第二屏的组件,若能预测用户行为,则可以
prefetch下一屏的组件。
由此引出另一话题,资源的加载是存在优先级的,具体的请看:蚂蚁金服数据体验技术 (opens new window)出的文章。
# 5.页面渲染机制
以下内容来源于:Web Fundamentals (opens new window)
HTML结构如下:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
第1步,根据
HTML文件,边下载边构建DOM树。(且会被js下载和执行阻塞)
Chrome-dev-tools流程图如下:此阶段为parse HTML
在控制台输入
document console.dir(document)1
2第2步,边下载边构建
CSSOM树,css文件和CSSOM树如下图所示:body { font-size: 16px } p { font-weight: bold } span { color: red } p span { display: none } img { float: right }1
2
3
4
5可以发现,
font-size有传导的作用
根据
DOM和CSSOM生成render tree渲染树计算(Recalculate Style阶段)注意:这里渲染树只会计算所有在页面上可见的
DOM内容,以及每个node上的CSSOM样式。
以上结束后,触发
DOMContentLoaded事件,说明render tree已经准备就绪,可以开始渲染。页面渲染,即如何将
render tree转化为屏幕上的真实pixels
三个阶段:
layout、paint、composite有了渲染树后,进入布局阶段(
layout)布局阶段:会计算相对于视口的位置,大小和尺寸。
布局完成后,浏览器会立即发出“
Paint”事件,将渲染树转换成屏幕上的像素。图层树计算,进入
composite阶段。
对应的
Chrome-dev-tools显示有:Try it (opens new window)
下面的结果是错的(忘记关插件了.......)

# 6.如何判定 reflow 和 repaint
对性能影响最大的是页面内容的渲染,大部分我们只考虑 Layout阶段,和 Paint 阶段。
Layout 阶段的修改:reflow回流。
Paint 阶段的修改:repaint重绘。
而Composite阶段,只要页面被修改了则必定会触发,一般不会考虑此阶段的性能优化。
这里的优化方案如下:
通过查阅
csstrigger网站 (opens new window),查阅不同的css样式,会触发的Layout、Paint、Composite的影响。
浏览器内核 渲染引擎 IE Trident Firefox Gecko Safari Webkit Chrome Blink(WebKit 的分支) Opera 原为 Presto,现为 Blink 这里还有一个需要关注:
Change from default。上面介绍浏览器的渲染机制的时候其实忽略了一点就是,其实浏览器内部已经存在一套默认的样式了,我们的CSS文件只是对原有样式的一层覆盖和修改。选择对页面渲染影响较小的
css样式,如:将left样式改为transform样式。transform:
left:
在
Chrome和Firefox中,修改以上属性可以提高页面的渲染性能。
# 7. VSCode 插件
推荐:css-triggers VSCode 插件,插件下载地址 (opens new window)
# 8.总结
Webkit 主流程,已经可以很好的概括本篇博客的内容了。
