浏览器的渲染

227 阅读7分钟

参考

浏览器渲染进程通过网络进程获取数据

  • 浏览器网络进程处理和服务器建立了TCP连接后,向服务器发送http请求;服务器响应给浏览器网络进程HTML文件。
  • 浏览器通过服务器的响应头Content-Type=text/html判断这是一个HTML文档。浏览器就可以将二进制格式转化为可读的HTML文件。将HTML元素转换为JS对象,对象创建完成,会根据元素的嵌套结构形成DOM树;
  • 浏览器网络进程将请求回来的数据转发给浏览器主进程,浏览器主进程发消息给渲染进程渲染进程网络进程建立管道,将数据传输给渲染进程
  • Chrome V8引擎就工作在渲染进程中

浏览器渲染引擎对HTML文本进行解析和渲染

  • 渲染模块比较复杂,所以分为多个子模块进行处理
  • 每个子模块都有输入内容,处理过程,输入内容
  • 所以输入HTML经过浏览器渲染模块处理后形成屏幕上的像素,就是渲染流水线

总结流程渲染流水线:

HTML文档交给渲染引擎后,渲染引擎将HTML解析为浏览器可识别的DOM树,将DOM树的每个节点附上计算好的CSS属性值,计算带有CSS的DOM节点的位置和大小生成布局树。

创建DOM树

  • 为什么要构建DOM树,因为浏览器无法直接使用HTML,所以需要将HTML转化为浏览器能够理解的DOM树。
  • HTML解析器将超文本和标签解析为DOM树 解析.png

DOM树.png

在开发者工作,Console控制台,输入document:document的结构就是DOM结构和HTML结构一致,和HTML不同的是DOM是保存在内存中的树状结构,我们可以直接使用JS来获取和修改。 image.png

通过JS修改DOM内容 document.querySelector('.title-content-title').innerHTML='nihao zhongguo';

细节:

  • DOM就是浏览器自己的语言,每个节点对象相连形成父子关系,后续浏览器对某个节点进行操作就会比较方便了
    • 遇到link标签就会向服务器发送请求,请求外部链接,如css文件
    • 遇到script标签,就会向服务器发送请求,请求js文件
    • 主线程会先暂停解析HTML文件,先执行JS文件中的JS代码,JS代码执行完成之后才会继续解析HTML文件
    • img标签、css文件、defer,不会影响主线程,而是会异步的加载
    • 先返回js文件或者css文件看具体情况,如果先返回js文件,解析js文件的时候发生阻塞,不能先执行js文件,必须等CSSOM构建完成,才能执行js文件,因为渲染树是需要DOM和CSSOM构建完成以后才能构建,JS是可以控制CSS样式的,所以这一步
    • 浏览器有一个预扫描线程,会提起那扫描html文件,会提前把css,js和字体文件下载下来,下载是异步的,不影响主线程 html文件解析遇到js文件.png

输入&输出

  • 输入:HTML
  • 输出:DOM树

样式计算: 根据CSS属性值计算每个DOM节点的样式

HTML加载CSS的三种方式:

  • 通过link引入CSS文件
  • <style>标签内的CSS
  • 元素的style属性内嵌的CSS

分为三步:

1.将CSS解析成浏览器能够理解的结构:styleSheets

Chrome控制台通过document.styleSheets可以查看到,目前没看到内联样式。

2. 将属性值标准化

p {color:red} red是浏览器无法理解的,需要将它转化为浏览器可以理解的标准化的计算值

  • 将red转化为rgba数值
  • 将em转化为px

3.计算DOM树中每个节点的具体样式

计算方法: 根据DOM的继承关系来计算样式

  • 样式来源的文件
  • user agent浏览器默认样式
  • 继承样式
  • 每个样式属性也会根据w3c设置一个默认值 CSS层叠样式
  • CSS样式来源于多个源的属性值的算法,这是CSS最大的特点,CSS的全程就是层叠样式表

输入&输出

  • 输入DOM节点
  • 这个阶段输出每个DOM节点的样式,并保存在computedStyle结构中。

布局阶段

计算DOM树中可见节点的位置和大小

  1. 创建布局树
  • 只包含可见的节点
  • 浏览器根据节点样式计算每个可见节点也就是没有设置display:none的节点位置和宽高,计算好了就可以在屏幕上绘制了
  1. 布局计算
  • 计算布局树节点的位置。计算完重新写入布局树

输入&输出

  • 输入:布局树
  • 输出:布局树 没有清晰的分离输入输出,这是Chrome需要优化的点

这样每个节点都拥有了样式和位置大小

生成图层树

页面的展示就是多个图层按照一定的顺序叠加在一起展示的。 查看开发者工具的Layers

浏览器会为哪些元素创建涂层?

  • 拥有层叠样式的CSS属性
  • 需要裁剪的地方
    • overflow
    • 滚动条会生成一层涂层
  1. 创建涂层
  2. 渲染引擎绘制涂层

查看图层绘制的过程

渲染引擎如何绘制涂层

涂层的绘制是由渲染进程的合成线程完成的。

  • 图层的绘制列表准备好了后
  • 主线程会把该绘制列表提交(commit)给合成线程,那么合成线程是如何工作的

图层可能很大,但是用户只会看到视口部分,如果绘制全部涂层开销会很大,所以合成线程会将图层划分为图块。

  • 合成线程会按照视口附近的图块优生成位图。生成位图的操作是由栅格化来执行的。栅格化就是将图块转化为位图,会结合GPU进程完成 -合成与显示:
  • 合成线程生成绘制图块DrawQuad命令,给浏览器主进程
  • 主进程viz组件接收合成线程DrawQuad命令,将页面内容绘制到内存中,最后再将内存显示在屏幕上

渲染流水线总结

  1. 渲染进程将HTML内容转化为可读懂的DOM 树
  2. 渲染引擎将CSS样式表转化为可读懂的styleSheets,计算出DOM树节点的样式
  3. 创建布局树,根据盒子模型计算出DOM树节点的布局信息,存放在computed
  4. 对布局树进行分层,创建图层树
  5. 为每个图层生成绘制列表,并将其提交给渲染进程的合成线程
  6. 合成线程将图层分成图块,并在栅栏化线程池中将图块转化为位图
  7. 合成线程发送绘制图块命令DrawQuad给浏览器进程
  8. 浏览器进程根据DrawQuad消息生成页面,并显示到显示器上

重排、重绘、合成

  • 更新元素的布局信息:重排
    • 主进程:DOM-styleSheets-Layler-Layler-Paint-合成 更新元素的布局信息:如果使用JavaScript或CSS修改了元素的属性,div.style.height重排需要更新渲染流水线重新来一遍。开销最大。
  • 更新元素的绘制属性:重绘
    • 主进程:DOM-styleSheets-Layler(跳过)-Layler(跳过)-Paint-图层合成(非主线程) 使用JavaScript修改元素的背景颜色等绘制属性,布局阶段不会重新执行,开销比重排小
  • 修改了一个既不要布局也不要绘制的属性:合成
    • 主进程:DOM-styleSheets-Layler(跳过)-Layler(跳过)-Paint(跳过)-图层合成(非主线程) 使用transform:tranlate实现动画效果,会跳过布局和绘制过程,直接进入图层合成,而且是在非主线程上合成的,没有占用主线程资源,所以开销最小,能够提升绘制效率。

defer属性的script标签

上面步骤完成后,设置了defer的script标签开始加载并执行

<script defer src={...} />

页面加载和渲染完成,用户可以进行页面交互

问题:如果下载CSS文件阻塞了,会影响DOM树的构成吗