浏览器页面渲染机制
# 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
主流程,已经可以很好的概括本篇博客的内容了。