浏览器的渲染原理
当你在地址栏输入内容回车后,浏览器进程中的UI线程会捕捉输入内容,如果访问的是网址,会启动一个网络线程来进行DNS解析;后面连接服务器获取数据;如果输入的不是网址而是关键词,就会使用默认配置的搜索引擎来进行查询
网络进程获取到数据后会使用safeBrowsing检查站点是否是恶意站点,如果是恶意站点则会提醒,阻止访问,当然你也可以强行访问(safeBrowsing是谷歌内部安全系统,比如检查数据来源ip是不是在谷歌黑名单内)
浏览器获取到数据后,通过IPC(Inter-Process Communication)通知浏览器进程,浏览器进程再通过IPC将数据传给渲染器进程的主线程,进行后续的渲染
渲染过程
1》解析html,HTML Parser
解析html,生成dom树(document object model 为有后续做准备,object操作起来比字符串方便)
解析过程中遇到css,会解析css,遇到js会执行js
大概过程:
转换:浏览器从磁盘或网络读取 HTML 的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成各个字符。
令牌化:浏览器将字符串转换成规定的各种令牌,例如,“”、“”,以及其他尖括号内的字符串。每个令牌都具有特殊含义和一组规则。
词法分析:发出的令牌转换成定义其属性和规则的“对象”。
DOM构建:由于 HTML 标记定义不同标记之间的关系,创建的对象链接在一个树数据结构内
2》样式计算
这一步会把样式表的属性值标准化,如颜色全转为rgb格式,em/rem转换为px单位,字体的bold转为700
样式计算的过程:
- 格式化样式表
- 标准化样式表
- 计算每个DOM节点具体样式
3》布局Layout
构建布局树 Layout Tree
Dom树和 布局树不是一一对应的;Dom树是和html对应的;而布局树是Dom树和计算好的样式生成的,和最后展示在屏幕上的节点对应;
比如display:none的节点没有集合信息,不会生成到Dom树;Dom树中不存在伪元素节点,但是伪元素content的内容会出现在布局树中,还有匿名行盒、匿名块盒等等都会导致DOM树和布局树无法一一对应
补充:内容必须在行盒中;行盒和块和不能相邻;不满足的时候就会有匿名盒子产生
4》分层Layer
生成图层树(Layer Tree),浏览器的 layers 可以看到分层信息
拥有层叠上下文属性的元素会被提升为单独一层
图层:
- 普通图层:正常文档流、absolute/fix布局的元素都在这一图层
- 复合图层:开启了硬件加速的元素,会位于新的图层,其重绘/重排(回流)不会影响普通图层。开启硬件加速的方法包括:
- 最常用的方式:
translate3d
、translateZ
-
opacity
属性/过渡动画(需要动画执行的过程中才会创建合成层,动画没有开始或结束后元素还会回到之前的状态) -
<video><iframe><canvas><webgl>
等元素 -
will-chang
属性
- 最常用的方式:
层叠上下文:
HTML根元素本身就具有层叠上下文
普通元素设置position不为static并且设置了z-index属性,会产生层叠上下文。
元素的 opacity 值不是 1
元素的 transform 值不是 none
元素的 filter 值不是 none
**will-change:**提前告诉浏览器在一开始就把元素放到新的图层,后面用GPU 渲染的时候,不需要做图层的新建
CSS硬件加速:GPU可以快速计算简单重复的任务
CSS大部分样式还是通过 CPU 来计算的,但CSS 中也有一些 3d 的样式和动画的样式,重复、简单且大量的计算任务,是由GPU 来计算的,不在主线程中进行;opacity 需要改变每个像素的值,符合重复且大量的特点,会新建图层,交给 gpu 渲染。transform 是动画,每个样式值的计算也符合重复且大量的特点
5》绘制Paint
主线程遍历Layout Tree,创建绘制记录表
渲染主线程的工作到此位置,上面步骤交给其他线程完成
将绘制信息交给合成器线程,合成器线程进行分块,并对每个块进行栅格化
6》分块 Tiles
分块将每一层分为多个小的区域,在合成线程中完成(也在渲染进程中)
为什么分块:
有时候,你的图层很大,或者说你的页面需要使用滚动条,然后页面的内容太多,这个时候需要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要,这样也能加快首屏展示的事件
因为后面图块(非视口内的图块)数据要进入 GPU 内存,考虑到浏览器内存上传到 GPU 内存的操作比较慢,即使是绘制一部分图块,也可能会耗费大量时间。针对这个问题,Chrome 采用了一个策略: 在首次合成图块时只采用一个低分辨率的图片,这样首屏展示的时候只是展示出低分辨率的图片,这个时候继续进行合成操作,当正常的图块内容绘制完毕后,会将当前低分辨率的图块内容替换。这也是 Chrome 底层优化首屏加载速度的一个手段;而且首先画的是视觉窗口内的内容
7》光栅化Raster
将图块转为位图
现在谷歌栅格化方案为合成,就是将页面的各个部分分块,分别进行栅格化,在专门的合成线程中进行
完成分块后,合成线程会将信息交给GPU进程,告诉完成光栅化
GPU会开启多个线程来完成光栅化,并且它优先处理靠近视口的块
- 图块是栅格化执行的最小单位
- 渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据
- 合成线程会选择视口附近的图块(tile),把它交给栅格化线程池生成位图
- 生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给
合成线程
栅格线程将每个图块转为位图然后合成器线程根据这些位图得到合成器帧帧
8》显示
合成器线程生成合成器帧,合成器帧frame通过IPC传给浏览器进程,浏览器进程通过IPC传给GPU进程,GPU进程将绘制信息提交给GPU硬件,完成屏幕成像
重绘与回流
重绘repaint
重绘是当节点需要更改外观而不会影响布局的
比如颜色改变、背景色变化
回流reflow
就是会影响布局的样式发生了改变
比如滚动页面、窗口变化、元素宽高改变等都会引起回流
阻塞问题
JS的加载与解析会不会阻塞html解析?
js会阻塞页面的渲染,GUI渲染线程与JS引擎线程互斥;但是不一定阻塞html解析
默认情况下使用script标签如果在body前面,是会阻塞html解析的,因为js可能会修改html解构(因为js可能会改变dom,所以他会暂停html和css的解析),如果js中调用了document.write()方法,那么之前的html解析就没有意义了;所以我们要将script标签放到合适的位置,或者加上defer或async
defer与async的区别
<script></script>
<script defer></script>
<script async></script>
1》async和defer的最主要的区别就是async是异步下载并立即执行,然后文档继续解析,defer是异步加载后解析文档,然后再执行脚本
2》defer脚本会在DOMContentLoaded和load事件之前执行
async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序
3》如果有多个声明了defer的脚本,则会按顺序下载和执行
如果有多个声明了async的脚本,其下载和执行也是异步的,不能确保彼此的先后顺序
<script type="module"></script>
默认行为与defer一致,唯一区别是会将脚本中import的其它脚本也一并加载完
<script type="module" async></script>
解析html的同时可以加载js,加载完后会立即执行脚本;而且会将脚本中import的其它脚本也一并加载完
注意:并不是非要等到HTML解析完才会触发绘制、显示,只要有完整CSS规则树+部分DOM树即可触发页面内容显示(尽管内容不全)
load事件和DOMContentLoaded事件
1》DOMContentLoaded事件
当纯 HTML 被完全加载以及解析时,DOMContentLoaded*事件会被触发,而不必等待样式表,图片或者子框架完成加载
2》load事件文章来源:https://www.toymoban.com/news/detail-469787.html
在整个页面及所有依赖资源如样式表和图片都已完成加载时触发文章来源地址https://www.toymoban.com/news/detail-469787.html
SS规则树+部分DOM树即可触发页面内容显示(尽管内容不全)
load事件和DOMContentLoaded事件
1》DOMContentLoaded事件
当纯 HTML 被完全加载以及解析时,DOMContentLoaded*事件会被触发,而不必等待样式表,图片或者子框架完成加载
2》load事件
在整个页面及所有依赖资源如样式表和图片都已完成加载时触发
到了这里,关于浏览器渲染原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!