关于前端:浏览器渲染机制

32次阅读

共计 6657 个字符,预计需要花费 17 分钟才能阅读完成。

残缺高频题库仓库地址:https://github.com/hzfe/aweso…

残缺高频题库浏览地址:https://febook.hzfe.org/

相干问题

  • 浏览器如何渲染页面
  • 有哪些进步浏览器渲染性能的办法

答复关键点

DOM CSSOM 线程互斥 渲染树 Compositing GPU 减速

当浏览器过程获取到 HTML 的第一个字节开始,会告诉渲染过程开始解析 HTML,将 HTML 转换成 DOM 树,并进入渲染流程。个别所有的浏览器都会通过五大步骤,别离是:

  1. PARSE:解析 HTML,构建 DOM 树。
  2. STYLE:为每个节点计算最终的无效款式。
  3. LAYOUT:为每个节点计算地位和大小等布局信息。
  4. PAINT:绘制不同的盒子,为了防止不必要的重绘,将会分成多个层进行解决。
  5. COMPOSITE & RENDER:将上述不同的层合成为一张位图,发送给 GPU,渲染到屏幕上。

为了进步浏览器的渲染性能,通常的伎俩是保障渲染流程不被阻塞,防止不必要的绘制计算和重排重绘,利用 GPU 硬件加速等技术来进步渲染性能。

知识点深刻

1. 浏览器的渲染流程

Chromium 的渲染流程的次要步骤如下图所示:

图片起源 Life of a Pixel

1.1 Parse 阶段:解析 HTML

构建 DOM 树

渲染过程主线程解析 HTML 并构建出结构化的树状数据结构 DOM 树,须要经验以下几个步骤:

  1. Conversion(转换):浏览器从网络或磁盘读取 HTML 文件原始字节,依据指定的文件编码(如 UTF-8)将字节转换成字符。
  2. Tokenizing(分词):浏览器依据 HTML 标准将字符串转换为不同的标记(如 <html>, <body>)。
  3. Lexing(语法分析):上一步产生的标记将被转换为对象,这些对象蕴含了 HTML 语法的各种信息,如属性、属性值、文本等。
  4. DOM construction(DOM 结构):因为 HTML 标记定义了不同标签之间的关系,上一步产生的对象会链接在一个树状数据结构中,以标识父子、兄弟关系。

构建 DOM 的流程如下图所示:

图片起源 Constructing the Object Model

次级资源加载

一个网页通常会应用多个内部资源,如图片、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 构建。

1.2 Style 阶段:款式计算

CSS 引擎解决款式的过程分为三个阶段:

  1. 收集、划分和索引所有样式表中存在的款式规定,CSS 引擎会从 style 标签,css 文件及浏览器代理款式中收集所有的款式规定,并为这些规定建设索引,以不便后续的高效查问。
  2. 拜访每个元素并找到实用于该元素的所有规定,CSS 引擎遍历 DOM 节点,进行选择器匹配,并为匹配的节点执行款式设置。
  3. 联合层叠规定和其余信息为节点生成最终的计算款式,这些款式的值能够通过 window.getComputedStyle() 获取。

在大型网站中,会存在大量的 CSS 规定,如果为每个节点都保留一份款式值,会导致内存耗费过大。作为代替,CSS 引擎通常会创立共享的款式构造,计算款式对象个别有指针指向雷同的共享构造。

附加了计算款式的 DOM 树,个别被称为 CSSOM(CSS Object Model):

图片起源 Constructing the Object Model

CSSOM 和 DOM 是并行构建的,构建 CSSOM 不会阻塞 DOM 的构建。但 CSSOM 会阻塞 JS 的执行,因为 JS 可能会操作款式信息。尽管 CSSOM 不会阻塞 DOM 的构建,但在进入下一阶段之前,必须期待 CSSOM 构建实现。这也是通常所说的 CSSOM 会阻塞渲染。

1.3 Layout 阶段

创立 LayoutObject(RenderObject)树

有了 DOM 树和 DOM 树中元素的计算款式后,浏览器会依据这些信息合并成一个 layout 树,收集所有可见的 DOM 节点,以及每个节点的所有款式信息。

Layout 树和 DOM 树不肯定是一一对应的,为了构建 Layout 树,浏览器次要实现了下列工作:

  1. 从 DOM 树的根节点开始遍历每个可见节点。
  • 某些不可见节点(例如 script、head、meta 等),它们不会体现在渲染输入中,会被疏忽。
  • 某些通过设置 display 为 none 暗藏的节点,在渲染树中也会被疏忽。
  • 为伪元素创立 LayoutObject。
  • 为行内元素创立匿名蕴含块对应的 LayoutObject。
  1. 对于每个可见节点,为其找到适配的 CSSOM 规定并利用它们。
  2. 产出可见节点,蕴含其内容和计算的款式。

图片起源 Render-tree Construction

布局计算

上一步计算了可见的节点及其款式,接下来须要计算它们在设施视口内的确切地位和大小,这个过程个别被称为主动重排。

浏览器的布局计算工作蕴含以下内容:

  1. 依据 CSS 盒模型及视觉格式化模型,计算每个元素的各种生成盒的大小和地位。
  2. 计算块级元素、行内元素、浮动元素、各种定位元素的大小和地位。
  3. 计算文字,滚动区域的大小和地位。
  4. LayoutObject 有两种类型:
  • 传统的 LayoutObject 节点,会把布局运算的后果从新写回布局树中。
  • LayoutNG(Chrome 76 开始启用)节点的输入是不可变的,会保留在 NGLayoutResult 中,这是一个树状的构造,相比之前的 LayoutObject,少了很大回溯计算,进步了性能。

1.4 Paint 阶段

Paint 阶段将 LayoutObject 树转换成供合成器应用的高效渲染格局,包含一个蕴含 display item 列表的 cc::Layers 列表,与该列表与 cc::PropertyTrees 关联。

构建 PaintLayer(RenderLayer)树

构建实现的 LayoutObject 树还不能拿去显示,因为它不蕴含绘制的程序(z-index)。同时,也为了思考一些简单的状况,如 3D 变换、页面滚动等,浏览器会对上一步的节点进行分层解决。这个处理过程被称为建设层叠上下文。

浏览器会依据 CSS 层叠上下文标准,建设层叠上下文,常见状况如下:

  1. DOM 树的 Document 节点对应的 RenderView 节点。
  2. DOM 树中 Document 节点的子节点,也就是 HTML 节点对应的 RenderBlock 节点。
  3. 显式指定 CSS 地位的节点(position 为 absolute 或者 fixed)。
  4. 具备通明成果的节点。
  5. 具备 CSS 3D 属性的节点。
  6. 应用 Canvas 元素或者 Video 元素的节点。

浏览器遍历 LayoutObject 树的时候,建设了 PaintLayer 树,LayoutObject 与 PaintLayer 也不肯定是一一对应的。每个 LayoutObject 要么与本人的 PaintLayer 关联,要么与领有 PaintLayer 的第一个先人的 PaintLayer 关联。

构建 cc::Layer 与 display items

浏览器会持续依据 PaintLayer 树创立 cc::Layer 列表。cc::Layer 是列表状构造,每个 layer 蕴含了个 DisplayItem 列表,每个 DisplayItem 蕴含了理论的 paint op 指令。将页面分层,能够让一个图层独立于其余的图层进行变换和光栅化解决。

  1. 合成更新(Compositing update)
  • 根据 PaintLayer 决定分层(GraphicsLayers)
  • 这个策略被称为 CompositeBeforePaint,将来会被 CompositeAfterPaint 代替。
  1. PrePaint
  • PaintInvalidator 进行生效查看,找出须要绘制的 display items。
  • 构建 paint property 树,该树能使动画、页面滚动,clip 等变动仅在合成线程运行,进步性能。
  

![图片](/img/bVcUYGK)

> 图片起源 Compositor Property Trees

  1. Paint
  • 遍历 LayoutObject 树并创立 display items 列表。
  • 为共享同样 property tree 状态的 display items 列表创立 paint chunks 分组。
  • 将后果 commit 到 compositor。
  • CompositeAfterPaint 将在此时决定分层。
  • 将 paint chunks 通过 cc::Layer 列表传递给 compositor。
  • 将 property 树转换为 cc::PropertyTrees。

下面的流程中,有两个不同的创立合成层的机会,一个是 paint 之前的 CompositeBeforePaint,该操作在渲染主线程中实现。一个是 paint 之后的 CompositeAfterPaint,后续创立 layer 的操作在 CC(Chromium Compositor)线程中实现。

1.5 合成 Compositing

合成阶段在 CC(Chromium Compositor)线程中进行。

commit

当 Paint 阶段实现后,主线程进入 commit 阶段,将 cc::Layer 中的 layer list 和 property 树更新到 CC 线程的 LayerImpl 中,commit 实现。commit 进行的过程中,主线程被阻塞。

tiling & raster

raster(光栅化)是将 display item 中的绘制操作转换为位图的过程。

光栅化的次要操作流程如下:

  1. tiling:将 layer 分成 tiles(图块)。因为有的 layer 可能很大(如整个文档的滚动根节点),对整层的光栅化操作代价低廉,且 layer 中有的局部是不可见的,会造成不必要的节约。
  2. tiles 是光栅化的根本单元。光栅化操作是通过光栅线程池解决的。离视口更近的 tiles 具备更高的优先级,将优先解决。
  3. 一个 layer 实际上会生成多种分辨率的 tiles。
  4. raster 同样也会解决页面援用的图片资源,display items 中的 paint ops 援用了这些压缩数据,raster 会调用适合的解码器来解压这些数据。
  5. raster 会通过 Skia 来进行 OpenGL 调用,光栅化数据。
  6. 渲染过程是运行在沙箱中的,不能间接进行零碎调用。paint ops 通过 IPC(MOJO)传递给 GPU 过程,GPU 过程会执行实在的 OpenGL(为了保障性能,在 Windows 上转为 DirectX)调用。
  7. 光栅化的位图后果保留在 GPU 内存中,通常作为 OpenGL 材质对象保留。
  8. 双缓冲机制:主线程随时会有 commit 到来,以后的光栅化行为在 pending tree(LayerImpl)上进行,一旦光栅化操作实现,将 pending tree 变为 active tree,后续的 draw 操作在 active tree 上进行。

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)。

最终的显示流程:

  1. viz 会收回 OpenGL 调用将合成帧中的 quads 发送到 GPU 线程的 backbuffer 中。
  2. 在新的模式中,viz 会应用 Skia 代替原始 OpenGL 调用。
  3. 在大部分平台上,viz 的输入也是双缓冲构造,draw 首先达到 backbuffer,通过 swapping 操作转换成 frontbuffer 最终显示在屏幕上。

线程对浏览器事件的解决

合成的长处是它在不波及渲染主线程的状况下实现的。合成器不须要期待款式计算或 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}
);

2. 浏览器渲染性能的优化

上一节中是一轮典型的浏览器渲染流程,在流程实现之后,DOM、CSSOM、LayoutObject、PaintLayer 等各种树状数据结构都会保留下来,以便在用户操作、网络申请、JS 执行等事件产生时,从新触发渲染流程。

2.1 缩小渲染中的重排重绘

浏览器从新渲染时,可能会从两头的任一步骤开始,直至渲染实现。因而,尽可能的缩短渲染门路,就能够取得更好的渲染性能。当浏览器从新绘制一帧的时候,个别须要通过布局、绘图和合成三个次要阶段。这三个阶段中,计算布局和绘图比拟费时间,而合成须要的工夫绝对少一些。

以动画为例,如果应用 JS 的定时器来管制动画,可能就须要较多的批改布局和绘图的操作,个别有以下两种办法进行优化:

  1. 应用适合的网页分层技术:如应用多层 canvas,将动画背景,静止主体,主要物体分层,这样每一帧须要变动的就只是一个或部分合成层,而不是整个页面。
  2. 应用 CSS Transforms 和 Animations:它能够让浏览器仅仅应用合成器来合成所有的层就能够达到动画成果,而不须要从新计算布局,从新绘制图形。CSS Triggers 中仅触发 Composite 的属性就是最优的抉择。

2.2 优化影响渲染的资源

在浏览器解析 HTML 的过程中,CSS 和 JS 都有可能对页面的渲染造成影响。优化办法包含以下几点:

  1. 要害 CSS 资源放在头部加载。
  2. JS 通常放在页面底部。
  3. 为 JS 增加 async 和 defer 属性。
  4. body 中尽量不要呈现 CSS 和 JS。
  5. 为 img 指定宽高,防止图像加载实现后触发重排。
  6. 防止应用 table, iframe 等慢元素。起因是 table 会等到它的 dom 树全副生成后再一次性插入页面中;iframe 内资源的下载过程会阻塞父页面动态资源的下载及 css, dom 树的解析。

图片起源 The Script Element

参考资料

  1. 浏览器的工作原理:旧式网络浏览器幕后揭秘
  2. 渲染页面:浏览器的工作原理
  3. Constructing the Object Model
  4. Inside a super fast CSS engine
  5. Render-tree Construction, Layout, and Paint
  6. Inside look at modern web browser(part 3)
  7. Inside look at modern web browser(part 4)
  8. DOM
  9. CSS
  10. Layout
  11. Paint
  12. how cc works
  13. Life of a Pixel

正文完
 0