我们前端搬砖工天天和浏览器打交道,我们写了布局写了样式,浏览器就哗啦哗啦给我们显示出来,可是显示的背后究竟做了些什么事情呢?我们这里只谈拿到资源之后的事情,首先当浏览器的网络线程接收到html文档后,会产生一个渲染任务,并把渲染任务传递给渲染主线程,在事件循环机制的作用下,渲染主线程开始执行渲染任务,开始渲染流程:
- 解析html:生成dom树,生成cssom树
- dom树和cssom结合计算
- 生成layout
- 分层
- 绘制
- 分块
- 光栅化
- 画
- 完成显示
我们接下来分别解析一下每个步骤都做了些什么事情
解析html(parse Html)
我们拿到了资源,解析html生成dom树、cssom树,浏览器就会生成对应的树形结构,如图
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
div {
width: 100px;
height: 100px;
}
div > span {
font-size: 20px;
color: white;
}
</style>
<style>
div {
background: purple
}
</style>
</head>
<body>
<div>
<span>111</span>
<span>222/span>
</div>
<p>this is</p>
<script></script>
</body>
</html>
解析成树
- dom树:根据页面的标签结构,依次解析成一个树形结构,这个还是比较好理解的,我们平时写的div各种都是相互嵌套,然后就形成了一棵dom树
- cssom树:
-
- css style sheet list:css的样式表,其实有几个种类:
-
-
- 内连样式表:
- 外部的样式表:
- 内部样式表:div: { color: red }
- 浏览器默认样式表:
-
-
- css style rule:这个下面就是各个选择器,选择器下面就是具体的样式设置了
css样式表小技巧
小技巧:除了浏览器的默认样式表,其他的样式表我们都能操作,平时我们可能只是操作内连的样式表,其实其他的两个样式表也可以操作通过
- document.styleSheets获取的所有样式表
- document.styleSheets[1].addRule('div', 'background: pink') 为所有的div增加规则,一些ui库应该会用到这些东西
解析遇到js
解析遇到js是会阻塞html的解析的,因为js是有可能会导致html的变动,所以需要先加载执行完js再继续执行html的解析
解析遇到css
解析遇到css是不会阻塞暂停的,渲染主线程会开辟一个预解析器,碰到css如果没有没有下载的(link外部的css),主线程会继续往前执行别的,css的预解析器会去和其他线程配合下载css,完成了下载预解析就会把css解析任务送回渲染主线程,主线程执行到css解析任务就正常解析,如图
计算样式(Computed style)
只是生成了两棵树还不够,需要结合dom树和cssom树计算出样式,例如div这个的宽高背景是什么,要两棵树结合生成有样式的dom树,所以渲染主线程会遍历dom树,依次为每个dom节点计算出出compute style浏览器里面可以看到:
- 会把颜色转成rgb,会把你的rem转成px等等
生成布局树(layout tree)
根据元素几何信息增减
元素的数值都计算好了,但是还不够就是layout tree和计算样式合成的dom树不是一一对应的,例如:
- dom树有个元素样式是diaply: none,那么layout tree就不会有这个元素
- css设置了伪类元素::before,::after这些,那么layout tree会加上这些元素
layout tree是要生成每个节点的几何信息,上面两个点,一个是没有几何信息所以去掉,伪类有几何信息所以要加上
自动生成匿名盒子
- 会给你生成一些匿名行盒和匿名块盒,这点怎么理解呢,因为你的html和css的语义不完全匹配,浏览器为了符合css规范,就会自动插入一些匿名盒子,规则如下
-
- 当内联元素(inline)或文本直接出现在一个**块级盒子(block box)**中,而这些内联内容并没有被内联容器包裹时,浏览器会自动生成一个匿名的块盒来容纳这些内容。(CSS 的规则要求,块盒只能包含块级子项或一个匿名块盒,不能直接混合内联内容,因此会自动生成匿名块盒。)
<div>
Hello <span>world</span>
</div>
<div>
<匿名块盒>
Hello
<span>world</span>
</匿名块盒>
</div>
-
- 当块盒中包含文本节点或内联内容,但这些内容没有包裹在显式的内联盒中时,会生成匿名行盒(为了正确计算 line box(行框)位置、对齐、换行等问题,浏览器必须将文本包装成 inline box。)
<p>This is some text.</p>
<p>
<匿名行盒>
This is some text.
</匿名行盒>
</p>
-
- 当一个容器中同时包含块级元素和内联元素时,浏览器会自动将内联部分包在匿名块盒中以分隔它们。
<div>
Some text
<div>A block</div>
More text
</div>
<div>
<匿名块盒>Some text</匿名块盒>
<div><匿名行盒>A block<匿名行盒></div>
<匿名块盒>More text</匿名块盒>
</div>
分层(Layer)
接着主线程会有个复杂的策略对整个layout tree进行分层,分层的好处在于以后发生改变可以只改变当前的层级,提高效率。滚动条、transform、opacity等样式都会影响分层的结果,也可以使用will-change的css属性去影响分层结果,但是不一定你用了就会分层
控制台工具layers里面可以看到分层:
绘制(paint)
为每一层的分层生成如何绘制的指令,然后渲染主线程的工作就到此为止了,剩余的工作就交给其他线程了
分块(Tile)、光栅化(Raster)、画(draw)
- 由合成线程把每一层分成多个小块,分块完成后会产生多个tile(瓦片),然后会把title下发给光栅线程池(Raster Worker Pool)由多个线程执行,这里实际就是调用gpu的多个线程完成光栅化,就是把每个tile变成位图,是有像素信息的
- 看到下图,page里面有个view就是我们的视窗,我们会优先去进行靠近视窗块的光栅化
- 光栅化得到位图之后,合成线程拿到位图就会生成一个个的指引(quad)然后交给gpu,gpu就会调用硬件去画出显示在屏幕的东西