浏览器的渲染流程

1. 将 HTML 解析为 DOM 树

由于浏览器并不能理解 HTML 文件, 故而当网络进程将服务器返回的 HTML 传输给渲染进程时, 渲染进程也会逐步解析 HTML 文件, 构造一个浏览器能够理解的 DOM 树

2. 将 CSS 解析为 CSSOM 树

浏览器并不能理解 CSS 文件的内容, 故而当网络进程将服务器返回的 CSS 文件传输给渲染进程时, 渲染进程也会解析 CSS 文件, 构造一个 CSSOM 树, 并将对应的样式标准化, 使浏览器能够理解该内容

3. 合并 DOM 和 CSSOM 为布局树

4. 绘制

4.1 分层

4.1.1 渲染对象 Render Object

Render Object 上实现了将 DOM 绘制进位图的方法, 负责绘制 DOM 的可见内容. 每个 Render Object 和 DOM 节点一一对应, 且 Render Object 和 DOM 一样是树形的

4.1.2 渲染层 Render Layer

每一个 z-index 不同的层叠上下文元素(或被裁剪过的元素)划分为一个渲染层
而不是层叠上下文的元素(或者没有被裁剪过的元素)则会与离其最近的祖先层叠上下文元素共享同一个渲染层

4.1.3 合成层/图层 Graphics Layer 与 Graphics Context

当一个渲染层满足如下的常见条件时, 会被提升为 Graphics Layer

  • 3D transfroms: translate3d, translateZ 等
  • video, canva, iframe 等元素
  • 通过 Element.animate() 实现的 opacity 动画转换
  • 通过 CSS 动画实现的 opacity 动画转换
  • position: fixed
  • 具有 will-change 属性
  • 对 opacity, transform, fliter, backdropfilter 应用了 animation 或 transition

4.2 绘制图层

4.2.1 生成绘制列表

渲染进程会将一个图层的绘制拆分为很多小的绘制指令, 然后再将这些指令按照顺序组成一个待绘制列表

在浏览器中, 可以通过开发者工具查看:

生成完待绘制列表后, 渲染进程会将待绘制列表从主线程提交给其渲染进程中的合成线程

5. 合成

5.1 栅格化

有些图层可能会很大, 而用户又只能看到很小一部分. 如果直接对所有的图层进行绘制的话, 就会产生太大的开销, 而且也没有必要.
基于这个原因, 合成线程不会一次性就将图层绘制完毕, 而是优先绘制图层的一小部分(以视口为基准), 栅格化过程如下(栅格化操作通常会使用 GPU 进行加速完成)

  1. 合成线程将每个图层划分为多个图块(tile)
  2. 合成线程优先为可视图块生成位图, 当使用 GPU 加速时, 通常在 GPU 中生成位图, 生成的位图也保存在 GPU 的内存中

5.2 提升到合成层的好处

我们构造一个在页面中不断移动的元素

Code
<html>
<style>
@keyframes move{
  0%{
    top: 0;
    left: 0;
  }
  50%{
    top: 500px;
    left: 500px;
  }
  75%{
    top: 0;
    left: 400px;
  }
  100% {
    top: 400px;
    left: 0px;
  }
}
@keyframes opacity{
  0%{
    background-color: aliceblue;
  }
  50%{
    background-color: aqua;
  }
  100%{
    background-color:blueviolet;
  }
}

#composited{
  width: 200px;
  height: 200px;
  background: red;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 3;
}
.both{
  animation: move 1s infinite, opacity 1s infinite;
}
.move{
  animation: move 1s infinite;
}
</style>

<div  id="composited" class="both">
  composited - animation
</div>
</html>  

当将移动的元素提升到合成层时, 可以看到绘制次数(Paint Count)都只有一次


而该对应的元素没有提升到合成层时, 我们看到: 不仅 #document 的绘制次数大量增加, 而且 GPU 的内存占用也比之前多不少

6. 页面渲染的阻塞

6.1 CSS 阻塞页面的渲染

当对 CSS 解析没有完成时, DOM 无法与 CSSOM 形成 Layout Tree, 故而页面无法渲染

注: CSS 的解析和 HTML 的解析是同步进行的, CSSOM 的构建不会阻塞 DOM 的构建. 只是 CSSOM 没有构建完毕会使得 Layout Tree 的构建被阻塞, 最终表现为页面的展示被阻塞

6.2 JavaScript 阻塞页面的渲染

  1. 当 JavaScript 没有加载完成时, 因为 script 代码有可能会更改 DOM 树结构, 故而 HTML 并不会继续解析, 而是等待 JavaScript 代码执行完毕之后再进行解析
  2. JavaScript 代码加载完成之后需要等待 CSS 代码的加载完成, 因为 JavaScript 代码执行时可能要操作 Style 样式, 当 CSS 没有加载完成时, JavaScript 代码不会执行

解决方法:

  • 在 script 标签上添加 syncdefer 字段
    • sync: JavaScript 代码加载完成后, 立马执行对应的 JavaScript 代码
    • defer: JavaScript 代码加载完成后, 等待页面渲染完毕后再执行
  • 采用预解析线程: 当 HTML 代码中需要加载 JavaScript 或 CSS 时, 预解析线程会对这些文件进行提前下载
posted @ 2022-11-16 19:29  小阁下  阅读(205)  评论(0编辑  收藏  举报