DOM
CSSOM
线程互斥
渲染树
Compositing
GPU 加速
当浏览器进程获取到 HTML 的第一个字节开始,会通知渲染进程开始解析 HTML,将 HTML 转换成 DOM 树,并进入渲染流程。一般所有的浏览器都会经过五大步骤,分别是:
为了提高浏览器的渲染性能,通常的手段是保证渲染流程不被阻塞,避免不必要的绘制计算和重排重绘,利用 GPU 硬件加速等技术来提高渲染性能。
Chromium 的渲染流程的主要步骤如下图所示:
图片来源 Life of a Pixel
构建 DOM 树
渲染进程主线程解析 HTML 并构建出结构化的树状数据结构 DOM 树,需要经历以下几个步骤:
<html>
, <body>
)。构建 DOM 的流程如下图所示:
次级资源加载
一个网页通常会使用多个外部资源,如图片、JavaScript、CSS、字体等。主线程在解析 DOM 的过程中遇到这些资源后会一一请求。为了加速渲染流程,会有一个叫做预加载扫描器(preload scanner)线程并发运行。如果 HTML 中存在 img 或 link 之类的内容,则预加载扫描器会查看 HTML parser 生成的标记,并发送请求到浏览器进程的网络线程获取这些资源。
JavaScript 可能阻塞解析
当 HTML 解析器发现 script 标签时,会暂停 HTML 的解析,转而开始加载、解析和执行 JavaScript。因为 JS 可能会改变 DOM 的结构。如果不想因 JS 阻塞 HTML 的解析,可以为 script 标签添加 defer 属性或将 script 放在 body 结束标签之前,浏览器会在最后执行 JS 代码,避免阻塞 DOM 构建。
CSS 引擎处理样式的过程分为三个阶段:
window.getComputedStyle()
获取。在大型网站中,会存在大量的 CSS 规则,如果为每个节点都保存一份样式值,会导致内存消耗过大。作为替代,CSS 引擎通常会创建共享的样式结构,计算样式对象一般有指针指向相同的共享结构。
附加了计算样式的 DOM 树,一般被称为 CSSOM(CSS Object Model):
CSSOM 和 DOM 是并行构建的,构建 CSSOM 不会阻塞 DOM 的构建。但 CSSOM 会阻塞 JS 的执行,因为 JS 可能会操作样式信息。虽然 CSSOM 不会阻塞 DOM 的构建,但在进入下一阶段之前,必须等待 CSSOM 构建完成。这也是通常所说的 CSSOM 会阻塞渲染。
创建 LayoutObject(RenderObject) 树
有了 DOM 树和 DOM 树中元素的计算样式后,浏览器会根据这些信息合并成一个 layout 树,收集所有可见的 DOM 节点,以及每个节点的所有样式信息。
Layout 树和 DOM 树不一定是一一对应的,为了构建 Layout 树,浏览器主要完成了下列工作:
布局计算
上一步计算了可见的节点及其样式,接下来需要计算它们在设备视口内的确切位置和大小,这个过程一般被称为自动重排。
浏览器的布局计算工作包含以下内容:
Paint 阶段将 LayoutObject 树转换成供合成器使用的高效渲染格式,包括一个包含 display item 列表的 cc::Layers 列表,与该列表与 cc::PropertyTrees 关联。
构建 PaintLayer(RenderLayer) 树
构建完成的 LayoutObject 树还不能拿去显示,因为它不包含绘制的顺序(z-index)。同时,也为了考虑一些复杂的情况,如 3D 变换、页面滚动等,浏览器会对上一步的节点进行分层处理。这个处理过程被称为建立层叠上下文。
浏览器会根据 CSS 层叠上下文规范,建立层叠上下文,常见情况如下:
浏览器遍历 LayoutObject 树的时候,建立了 PaintLayer 树,LayoutObject 与 PaintLayer 也不一定是一一对应的。每个 LayoutObject 要么与自己的 PaintLayer 关联,要么与拥有 PaintLayer 的第一个祖先的 PaintLayer 关联。
构建 cc::Layer 与 display items
浏览器会继续根据 PaintLayer 树创建 cc::Layer 列表。cc::Layer 是列表状结构,每个 layer 包含了个 DisplayItem 列表,每个 DisplayItem 包含了实际的 paint op 指令。将页面分层,可以让一个图层独立于其他的图层进行变换和光栅化处理。
合成更新(Compositing update)
PrePaint
PaintInvalidator 进行失效检查,找出需要绘制的 display items。
构建 paint property 树,该树能使动画、页面滚动,clip 等变化仅在合成线程运行,提高性能。
Paint
上面的流程中,有两个不同的创建合成层的时机,一个是 paint 之前的 CompositeBeforePaint,该操作在渲染主线程中完成。一个是 paint 之后的 CompositeAfterPaint,后续创建 layer 的操作在 CC(Chromium Compositor)线程中完成。
合成阶段在 CC(Chromium Compositor)线程中进行。
commit
当 Paint 阶段完成后,主线程进入 commit 阶段,将 cc::Layer 中的 layer list 和 property 树更新到 CC 线程的 LayerImpl 中,commit 完成。commit 进行的过程中,主线程被阻塞。
tiling & raster
raster(光栅化)是将 display item 中的绘制操作转换为位图的过程。
光栅化的主要操作流程如下:
draw
当所有的 tiles 都完成光栅化后,会生成 draw quads(绘制四边形)。每个 draw quads 是包含一个在屏幕特定位置绘制 tile 的命令,该命令同时考虑了所有应用到 layer tree 的变换。每个四边形引用了内存中 tile 的光栅化输出。四边形被包裹在合成帧对象(compositor frame object)中,然后提交(submit)到浏览器进程。
display compositor(viz,visual 的简称)
viz 位于 GPU 进程中,viz 接收来自浏览器的合成帧,合成帧来自多个渲染进程,以及浏览器自身 UI 的 compositor。
合成帧和屏幕上将要绘制的位置关联,该位置叫做 surface。surface 可以嵌套其他 surface,浏览器 UI 的 surface 嵌套了渲染进程的 surface,渲染进程的 surface 嵌套了其他跨域 iframes(同源的 iframe 共享相同的渲染进程) 的 surface。viz 同步传入的帧,并处理嵌套 surfaces 的依赖(surface aggregation)。
最终的显示流程:
线程对浏览器事件的处理
合成的优点是它在不涉及渲染主线程的情况下完成的。合成器不需要等待样式计算或 JavaScript 执行。只和合成相关的动画被认为是获得流畅性能的最佳选择。同时,合成器还负责处理页面的滚动,滚动的时候,合成器会更新页面的位置,并且更新页面的内容。
当一个没有绑定任何事件的页面发生滚动时,合成器可以独立于渲染主线程之外进行合成帧的的创建,保证页面的流程滚动。当页面中的某一区域绑定了 JS 事件处理程序时,CC 线程会将这一区域标记为 Non-Fast Scrollable Region。如果事件来自于该区域之外,则 CC 线程继续合成新的帧,而无需等待主线程。
在开发中,我们通常会使用事件委托来简化逻辑,但是这会使整个绑定事件的区域变成 Non-Fast Scrollable Region。为了减轻这种情况对滚动造成的影响,你可以传入 passive: true
选项到事件监听器中。
document.body.addEventListener(
"touchstart", (event) => { if (event.target === area) { event.preventDefault(); } }, { passive: true });
Copy
上一节中是一轮典型的浏览器渲染流程,在流程完成之后,DOM、CSSOM、LayoutObject、PaintLayer 等各种树状数据结构都会保留下来,以便在用户操作、网络请求、JS 执行等事件发生时,重新触发渲染流程。
浏览器重新渲染时,可能会从中间的任一步骤开始,直至渲染完成。因此,尽可能的缩短渲染路径,就可以获得更好的渲染性能。 当浏览器重新绘制一帧的时候,一般需要经过布局、绘图和合成三个主要阶段。这三个阶段中,计算布局和绘图比较费时间,而合成需要的时间相对少一些。
以动画为例,如果使用 JS 的定时器来控制动画,可能就需要较多的修改布局和绘图的操作,一般有以下两种方法进行优化:
在浏览器解析 HTML 的过程中,CSS 和 JS 都有可能对页面的渲染造成影响。优化方法包括以下几点:
图片来源 The Script Element
本站文章用于学习交流
新浪微博 | QQ群1:161644793qq | QQ群2:98711210
网站地图 | 网站统计
Copyright 2011 - 2021 paocode.com All Rights Reversed. 浙ICP备19041980号
瞎猫内容中心