依然是网课学习,做的笔记,比较乱哈
感谢duyi的袁老师
在浏览器地址栏输入网址后,通过网络获取到html文档然后进行渲染工作
浏览器是如何渲染页面的?
当浏览器的网络线程收到html文档后,会产生一个渲染任务,并将该任务传给渲染主线程的消息队列
在时间循环的机制下,渲染主线程将消息队列中的渲染任务取出,开始进行渲染的流程
浏览器的渲染流程分为:HTML解析->样式计算->布局->分层->绘制->分块->光栅化->画
1.解析html-parse html
html文档会被解析为dom树(document object model)和cssom(css object model)树
dom树是一个对象,通过console.dir(document)即可查看
cssom树根节点为StyleSheetList
一个页面中有行内样式,内联样式,外联样式,浏览器默认样式每多一个样式,StyleSheetList下就会多一个子节点(CSSStyleSheet),每个子节点下有多个规则对象(CSSStyleRule)
我们可以通过stylesheetlist来操作样式,例如进入百度首页,在控制台输入[document.styleSheets[0].addRule('div','border:1px solid red')]这样该页面的所有div都会有边框样式了
解析过程中遇到css就解析,遇到js就执行,为提高效率开始解析前会启动一个预解析线程,先下载html中的外部css和js文件.
如果主线程解析到<link>位置,此时如果外部css还没有下载解析完毕,主线程不会等待,会继续解析后面的html.这是因为下载和解析css的工作是在预解析线程中进行的.这是css不会阻塞html解析的根本原因.
如果主线程解析到script位置,会停止解析html,等待js文件下载好,并将全局代码解析完成后,才能继续解析html.这是因为js代码的执行过程中可能会修改当前的DOM树,所以DOM树的生成必须暂停.这就是js会阻塞html解析的根本原因.
第一步完成后,会得到dom树和cssom树,浏览器的默认样式,内部样式,外部样式,行内样式都会包含在cssom树中.
2.样式计算 Recalculate Style
主线程会遍历到的DOM树,依次为树中的每个节点计算出它的最终样式(计算后的样式),称之为computed style.
最终样式通过js获取,可以使用 getComputedStyle()
比如单纯写一个div,不加任何样式,但是他还是有样式的,浏览器会将默认样式赋予这个div,在浏览器中的开发工具中可以看到div的最终样式,如图
在此过程中,很多预设值会变成绝对值,比如red会变成rgb(255,255,0);相对单位会变成绝对单位,比如em变成px.
本阶段完成后,会得到一个带有样式的DOM树.
css属性值的计算过程
层叠
继承
视觉格式化模型
盒模型
包含块
3.布局 Layout
布局阶段会依次遍历DOM树的每一个节点,计算节点的几何信息.例如节点的宽高,相对包含块的位置.
大部分时候,DOM树和布局树并非一一对应.
比如display:none的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然DOM树中不存在这些伪元素节点,但是他们有用几何信息,所以会生成到DOM树中.还有匿名行盒,匿名块盒等等都会导致DOM树和布局树无法一一对应.
块盒:例如h1-h6,div,p,ul,ol,li,dl,dt,dd,header,main,footer等,像容器一样的就是块盒,块盒独占一行,display是block
行盒:例如a,span,em,strong,img,input,label,textarea,select,button等,行盒不换行,display是inline
内容必须在行盒中,不能放到块盒
行盒 与 块盒不能相邻
js虽然不能直接获取布局树中的对象内容,但是可以间接获取一些,例如:
document.body.clientWidth
4.分层 Layer
主线程会使用一套复杂的策略对整个布局树进行分层
分层的好处在于,将来某一层发生变化后,只会对该层进行处理,从而提高效率
滚动条,堆叠上下文,transform,opacity等样式都会多多少少影响分层结果,也可以通过will-change属性更大程度的影响分层结果,例如:
<div class="dan-ceng"></div>
<style>
.dan-ceng{
will-change:transform;
}
</style>
这样,此div就单独为一个层了
5.绘制 Paint
主线程会为每个层生成绘制指令集,用于描述这一层的内容应该如何画出来.
指令就是告诉浏览器在什么位置画一个什么东西
完成绘制后,主线程将每个层的绘制信息提交给合成线程,剩余的工作将由合成线程完成.
6.分块 Tiling
合成线程首先对每个图层分块,将其划分为更多的小区域.
他会从线程池中拿取多个线程来完成分块工作.
7.光栅化 Raster
分块完成后,进入光栅化阶段.
合成线程会将块信息交给GPU进程,以极高的速度完成光栅化.
GPU进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块.
光栅化的结果,就是一块一块的位图.
8.画 Draw
最后就是画.
合成线程拿到每个层,每个块的位图后,生成一个个[指引 quad]信息.
指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转 缩放等变形.
变形发生在合成线程,与渲染主线程无关,这就是 transform 效率高的本质原因.
合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像.
完整过程
什么是 reflow?
reflow 的本质就是重新计算 layout 树以及8个步骤中的后续流程.
当进行了 会影响布局树 的操作后,需要重新计算布局树,会引发 reflow.
为了避免连续多次的操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再统一进行计算.
所以,改动属性造成的 reflow 是异步完成的.
正因如此,当 js 获取布局属性时,就可能造成无法获取到最新的布局信息.
浏览器在反复权衡下,最终决定获取属性立即 reflow.
什么是repaint?
repaint 的本质就是重新根据分层信息计算了绘制指令.
当改动了可见样式后,就需要重新计算,会引发 repaint.
由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint.
为什么transform效率高
因为 transform 既不会影响布局也不会影响绘制指令,他影响的只是渲染流程的最后[draw]阶段
由于 draw 阶段在合成线程中进行,所以 transform 的变化几乎不影响渲染主线程.反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化.