输入一个URL做了什么?--- 页面渲染篇
渲染过程
解析HTML构建DOM树
首先,发起请求拿到页面 HTML 内容,这个内容它是0/1这样的原始 字节流
接着,浏览器拿到这些 HTML 的原始字节,根据文件的指定编码 (例如 UTF-8) 将它们转换成各个 字符
然后通过词法解析:我们把字符流解析成了 词 (Token)
语法解析:开始结束标签配对、属性赋值好、父子关系这些都连接好了,最终就构成了 DOM
树
解析CSS构建CSSOM树
CSS
字节转换成字符,接着词法解析与法解析,最后构成 CSS对象模型(CSSOM)
的树结构
节点样式是可以继承的
所以在构建的过程中浏览器得递归 DOM
树来确定元素到底是什么样式,为了 CSSOM
的完整性,只有等构建完毕才能进入到下一个阶段,所以就算 DOM
已经构建完了,也得等 CSSOM
,然后才能进入下一个阶段
所以 CSS
的加载速度与构建 CSSOM
的速度会影响首屏渲染速度,这就是我们常说的 CSS
资源的加载会阻塞渲染
怎么优化?DOM树要小,CSS尽量用 id
和 class
少直接用标签????
解析JavaScript脚本
「JS会对DOM节点进行操作,浏览器无法预测未来的DOM节点的具体内容,为了防止无效操作,节省资源,只能阻塞DOM树的构建」
若不阻塞DOM树的构建,若 JS 删除了某个DOM节点A,那么浏览器为构建此节点A花费的资源就是无效的
若在 HTML 头部加载 JS 文件,由于 JS 阻塞,会推迟页面的首绘,所以为了加快页面渲染,一般将 JS 文件放到HTML 底部进行加载,或是对 JS 文件执行 async
或 defer
加载
构建渲染树/呈现树(Render Tree)
CSSOM 树
和 DOM 树
合并成渲染树,渲染树
只包含渲染网页所需的节点,然后用于计算每个可见元素的布局,并输出给绘制流程,将像素渲染到屏幕上
-
浏览器首先会从DOM树的根节点开始遍历每个可见节点
-
- 例如脚本标记、元标记等有些节点不可见,因为它们不会体现在渲染输出中,所以会被忽略
- 某些节点通过 CSS 隐藏,因此在渲染树中也会被忽略,例如一个
span
标签有display: none
属性,也会被忽略
-
对于每个可见节点,找到其对应的的 CSSOM 规则并应用它们
-
输出可见节点,连同其内容和计算的样式
布局(Layout)
计算了哪些节点应该是可见的以及它们的计算样式,但我们还没有计算它们在设备 视口[2] 内的确切位置和大小,这就是 布局
( Layout ) 阶段,也称为 自动重排
或 回流
( Reflow )
绘制(Painting)
它们的计算样式以及几何信息,我们将这些信息传递给最后一个阶段将渲染树中的每个节点转换成屏幕上的实际像素,也就是俗称的 绘制
或 栅格化
重绘(Repaint)
元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了,这叫做 重绘
( Repaint )
回流 (Reflow)
上面我们已经说过了 回流
,当然也叫 重排
,要知道,回流
一定伴随着 重绘
,重绘
却可以单独出现,对比来看,显然回流的成本开销要高于重绘,而且一个节点的回流往往还会导致子节点以及同级节点的回流,所以优化方案中一般都包括,尽量避免 回流
什么会引起回流?
- 页面渲染初始化
- DOM结构改变,比如删除了某个节点
- render树变化,比如减少了padding
- 窗口
resize
如何减少和避免回流重绘
回流开销太大了,那么我们肯定是要优化的
-
减少逐项更改样式,最好一次性更改
style
, -
避免循环操作DOM,让DOM离线后再修改
- 创建一个documentFragment,在上面操作dom后,再添加到window.document上
- 先把DOM节点display:none, 修改后再重新显示出来
- 克隆一个DOM节点,修改之后与在线的节点相替换
-
将复杂的元素绝对定位或固定定位,是他们脱离文档流,否则回流代价很高
-
改变字体大小也会引发回流,应避免
-
table布局,微小改动也会重新布局,少用为好
https://csstriggers.com/
合成(Composite)
浏览器会将各层信息发送给GPU,GPU将各层合成,显示在屏幕上
浏览器渲染方式
-
绘图上下文
- 2D图形上下文
- 3D图形上下文
-
网页三种渲染
- 软件渲染
- 使用软件绘图的合成化渲染
- 硬件加速的合成化渲染
软件渲染技术
- 1、绘制该层中所以块的背景和边框
- 2、绘制浮动内容
- 3、前景,内容部分、轮廓、字体颜色、大小等
硬件加速技术
硬件加速技术是指使用 GPU 的硬件能力来帮助渲染网页 ( GPU的作用主要是用来绘制3D图形并且性能很 nice )
页面渲染优化
-
HTML文档结构层次尽量少,最好不超过六层
-
JS脚本尽量后放
-
样式结构层次尽量简单
-
在脚本中尽量减少DOM操作,尽量访问离线DOM样式信息,避免过度触发回流
-
减少JS代码修改样式,使用class或样式操作动画
-
不使用table布局
-
CSS动画中尽量只使用transform和opacity,不会发生重排和重绘
最后
发起一个请求,拿到页面后,下载玩的网页交给浏览器内核(渲染进程)之后
- 首先,根据顶部定义的DTD类型进行对应的解析方式
- 渲染进程内不是多线程的,网页的解析将会交给内部的GUI渲染线程处理
- 渲染线程中的HTML解析器,将HTML网页和资源从字节流解释转换成字符流
- 通过词法分析器将字符流解释成词
- 经过语法分析器根基词构建成节点,最后通过这些节点组件一个DOM树
- 这个过程中,如果遇到DOM节点是JS代码,就会调用JS引擎,对JS进行解释执行,此时由JS引擎和GUI渲染线程互斥,GUI渲染线程就会被挂起,渲染过程停止,如果JS代码的运行中对DOM进行了修改,那么DOM的构建需要从新开始
- 如果节点需要依赖其他资源,图片/CSS等等,就会调用网络模块的资源加载器来加载它们,它们是异步的,不会阻塞当前DOM树的构建
- 如果遇到的是 JS 资源URL(没有标记异步),则需要停止当前DOM的构建,直到 JS 的资源加载并被
JS引擎
执行后才继续构建DOM - 对于CSS,CSS解释器会将CSS文件解释成内部表示结构,生成CSS规则树
- 然后合并CSS规则树和DOM树,生成 Render渲染树,也叫呈现树
- 最后对 Render树进行布局和绘制,并将结果通过IO线程传递给浏览器控制进程进行显示