浏览器工作原理(三):渲染过程

之前提到过,凡是需要渲染主线程执行的任务,都要进入事件循环排队。当网络进程将 html 文档下载到本地后,会创建一个渲染任务进入事件循环,然后由渲染主线程开始渲染工作。

在浏览器的众多进程线程中,渲染核心相关的如下图。

整个渲染过程如下图所示。

下面我们介绍这些步骤。

解析 html 和 css

渲染的第一步就是将 html 文本解析成树结构,称为文档对象模型。

在浏览器中,通过 document 对象可以访问到 dom 树的各个节点。

同样地,css 代码也会被解析成树结构,称为 CSSOM。

StyleSheetList 是根节点,下面是多个 CSSStyleSheet 对象,每个 CSSStyleSheet 对应代码中的内部或外部样式表。

javascript 复制代码
document.styleSheets // 根节点 StyleSheetList

我们可以用 JS 操作 CSSOM,从而修改或动态添加样式。

javascript 复制代码
document.styleSheets[0].addRule('div', 'border: 1px solid #000') // 选择器和样式声明

值得一提的是,为了提高效率,浏览器会启动一个预解析线程快速找到 html 中的 css 链接并下载解析,如果是 JS 文件,则必须停下等待其下载且执行完毕,这是因为 css 解析不影响 html 解析而 JS 会影响。

样式计算

DOM 和 CSSOM 都准备好后,主线程的下一步是样式计算。其实就是计算每个 dom 节点的最终样式,这个最终样式会附着于该 dom 节点上,称为 computed style。

在 chrome 中我们可以查看一个节点的 computed style,也可以用 getComputed 方法获取。

javascript 复制代码
getComputedStyle(document.body)

布局 Layout

现在我们有了一棵包含 computed style 的 dom 树,下一步是计算布局 ( layout )。

layout 过程就是根据 dom 树中每个节点的 computed style,计算节点的尺寸和位置。layout 的计算过程和视觉格式化模型息息相关。

dom 树和 layout 树不是一一对应的。比如 display: none 的节点不存在于 layout 树中;伪元素不存在于 dom 树,但会存在于 layout 树。

分层 Layer

分层类似图片编辑里的图层概念,主线程将它认为"经常一起变化"的节点生成一个新图层。

将哪些节点作为一个分层是浏览器决定的,我们也可以给浏览器一些提示,从而影响它的分层决策。跟堆叠上下文相关的 css 属性,比如 z-index、opacity、transform、will-change 会影响浏览器的分层结果。其中影响较大的是 will-change 属性。

css 复制代码
.div {
  will-change: transform; /* transform 属性会经常改变 */
}

以上属性提示浏览器,transform 属性会经常改变,浏览器会决定是否将这个 div 单独作为一层。

分层可以提高重新渲染的效率,但是分层需要占用较多内存,因此浏览器会对层数做取舍,兼顾内存和效率。

绘制 Paint

所谓绘制,就是为每个 layer 生成一系列绘制指令。绘制指令类似于 canvas 中的操作。事实上,canvas 就是浏览器将绘制功能暴露给开发者而来,也就是说,canvas 操作的就是绘制过程。

以下是绘制指令的一种方式。

makefile 复制代码
Draw Rect
pos: x, y, w, h
color: red

到目前为止我们介绍的是渲染主线程的操作,目前屏幕上仍未得到像素点,只有一系列绘制指令,这些指令会交给合成线程处理。

分块 Tiling

GPU 擅长并行执行,为提高光栅化和画的效率,我们需要对 layer 进行分块,也就是将每个 layer 层分成多个小区域,这样有利于并行操作。

分块在合成线程中完成,它是渲染进程的线程。合成线程会在线程池中获取多个分块器(线程),它们同时工作,提升分块效率。

关于线程池,就是里面有多个创建好的线程,使用者可以申请其中空闲的线程使用,如果没有空闲线程,则排队等待。这是为了减少频繁创建回收线程的开销,同时控制线程的数量,避免创建太多线程。

光栅化 Raster

光栅化就是将每个 tile 变成位图,且为了提高体验,浏览器会优先处理靠近视口的 tile。

光栅化是在浏览器的 GPU 进程中执行的,GPU 进程内也会开启多个线程执行。

画 Draw

光栅化完成后,合成线程计算每个位图在屏幕上的位置(quad 信息),同时进行 css 的 transform 转换。这些信息会交给 GPU 进程,然后由 GPU 进程进行系统调用,从而交给显卡,最终呈现在屏幕上。

之所以要转交给 GPU 进程,是因为合成线程所在的渲染进程是在沙盒中运行的(安全策略),无法进行系统调用。

参考

How does browser work step by step [latest] --- rendering phase (part 3)

相关推荐
林太白5 分钟前
Zustand状态库(简洁、强大、易用的React状态管理工具)
前端·javascript·react.js
Juchecar9 分钟前
Vue3 模板引用 useTemplateRef 详解
前端·vue.js
鼓浪屿11 分钟前
vue3的组件通信方式
前端
念旧Zestia29 分钟前
Oxc 家族 vs Biome——定位、能力与底层差异综述
前端
YuJie30 分钟前
vue3 无缝滚动
前端·javascript·vue.js
Juchecar30 分钟前
Vue3 表单输入 v-model 指令详解
前端·vue.js
晴空雨38 分钟前
Emmet 完全指南:让 HTML/CSS 开发效率提升 10 倍
前端·html
小野鲜40 分钟前
前端打开新的独立标签页面,并且指定标签页的大小,管理新标签页面的打开和关闭(包含源码和使用文档)
前端·javascript
一枚前端小能手41 分钟前
🌐 Web应用也想有原生App的体验,PWA来实现
前端·pwa
十五_在努力1 小时前
参透 JavaScript —— 解析浅拷贝、深拷贝及手写实现
前端·javascript