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

之前提到过,凡是需要渲染主线程执行的任务,都要进入事件循环排队。当网络进程将 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)

相关推荐
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发2 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
金创想4 小时前
chrome主页被被篡改的修复方法
chrome·主页篡改
ekskef_sef4 小时前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6414 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js