Fork me on GitHub
秋染蒹葭

浏览器渲染机制之一:基础概念

对于web前端开发者,浏览器再熟悉不过了。我们可能都知道浏览器含有一个渲染引擎,浏览器会根据 HTML文件来进行解析与渲染,最终再将页面呈现在用户面前。但是其具体的渲染原理和流程估计也有很多同学不清楚。然而前端开发者很有必要了解浏览器的工作工作机制。

什么是浏览器

浏览器可以被认为是使用最广泛的软件,在我们了解渲染机制之前,我们先来了解了解我们再熟悉不过的浏览器,浏览器的主要功能是将用户所请求的web资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是HTML,也包括PDF、image及其他格式

主流浏览器

StatCounter 的最新数据我们可以看到,现在市面有六种主流桌面浏览器:Chrome、Firefox、IE、Safari、Edge、Opera。

手机端的情况是chrome依旧强势,uc也不错,但是…就不说了。

如果是全平台UC Browser也是占了很大的市场。(看着IE的占有率越来越低Chrome占有率越来越高,也甚是欣慰 ,当然了国内情可能会差一点)

浏览器的主要构成

浏览器的主要组件包括:

  • 用户界面 (User Interface) - 包括地址栏、后退/前进按钮、书签目录等,也就是你所看到的除了用来显示你所请求页面的主窗口之外的其他部分。
  • 浏览器引擎 (Browser engine) - 用来查询及操作渲染引擎的接口。
  • 渲染引擎 (Rendering engine) - 负责解析用户请求的内容(如HTML或XML,渲染引擎会解析HTML或XML,以及相关CSS,然后返回解析后的内容)
  • 网络(Networking)- 用来完成网络调用,例如http请求,它具有平台无关的接口,可以在不同平台上工作。
  • UI后端(UI backend) - 用来绘制类似组合选择框及对话框等基本组件,具有不特定于某个平台的通用接口,底层使用操作系统的用户接口。
  • JS解释器(JavaScript interpreter) - 用来解释执行JS代码。
  • 数据存储 (Data storage) - 属于持久层,浏览器需要在硬盘中保存类似cookie的各种数据,HTML5定义了web database技术,这是一种轻量级完整的客户端存储技术

输入网址到页面呈现

不考虑重定向的话,输入网址到页面呈现大致有下面五步:

  • DNS 查询
  • TCP 连接
  • HTTP 请求即响应
  • 服务器响应
  • 客户端渲染

本篇文章的重点就在第五步即客户端渲染即浏览器渲染

浏览器渲染

渲染引擎的职责就是渲染,即在浏览器窗口中显示所请求的内容。默认情况下,渲染引擎可以显示html、xml文档及图片,它也可以借助插件(一种浏览器扩展)显示其他类型数据,例如使用PDF阅读器插件,可以显示PDF格式

浏览器内核

各大主要浏览器使用内核也是有差别的,大致可以分为以下几类:

  • Trident内核: IE

  • Gecko内核:FireFox

  • Webkit内核:Chrome,Safari

关键渲染路径

当浏览器接收到服务器接返回的一个HTML页面,到屏幕上渲染出来要经过很多个步骤。浏览器完成这一系列的运行(即渲染出来),我们称之为“关键渲染路径”(Critical Rendering Path) 下面是渲染引擎在取得内容之后的基本流程:

1
2
3
// / 执行JavaScript \
// / \
// 解析html以构建dom树 -> 解析css构建cssom树 -> 构建render树 -> 布局render树 -> 绘制render树

接下来我们认识几个概念:

  • DOMTree:浏览器将HTML解析成树形的数据结构。
  • CSSRuleTree:浏览器将CSS解析成树形的数据结构。CSSOM是CSS Object Model的缩写,它和DOM类似,但是只针对CSS而不是HTML。

  • RenderTree: DOM和CSSOM合并后生成Render Tree。
  • layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
  • painting: 按照算出来的规则,通过显卡,把内容画到屏幕上。
  • reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
  • repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。

渲染流程图

Webkit渲染引擎流程如下图:

Gecko渲染引擎流程如下图:

从上面两个图可以看出,尽管webkit和Gecko使用的术语稍有不同,他们的主要流程基本相同:

  • 浏览器会将HTML解析成一个DOM树,DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。
  • 将CSS解析成 CSS Rule Tree 。
  • 根据DOM树和CSS Rule Tree来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像Header或display:none的东西就没必要放在渲染树中了。
  • 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为layout,顾名思义就是计算出每个节点在屏幕中的位置。
  • 再下一步就是绘制,即遍历render树,并使用UI后端层绘制每个节点。

二者不同之处:

  • Gecko称可见的格式化元素组成的树为frame树,webkit则使用render树这个名词来命名由渲染对象组成的树。
  • Webkit中元素的定位布局称为布局,而Gecko中称为回流。
  • Webkit称利用dom节点及样式信息去构建render树的过程为attachment,Gecko在html和dom树之间附加了一层,这层称为内容池,相当制造dom元素的工厂

下面将讨论流程中的各个阶段

构建DOM树

文档对象模型 (DOM) 是HTML和XML文档的编程接口。它提供了对文档的结构化的表述,并定义了一种方式可以使从程序中对该结构进行访问,从而改变文档的结构,样式和内容。DOM 将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。简言之,它会将web页面和脚本或程序语言连接起来。

浏览器从网络或硬盘中获得 HTML字节数据后会经过一个流程将字节解析为 DOM树

假设浏览器获取返回的如下HTML文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="./theme.css"></link>
<script src="./config.js"></script>
<title>关键渲染路径</title>
</head>
<body>
<h1 class="title">关键渲染路径</h1>
<p>关键渲染路径介绍</p>
<footer>@copyright2017</footer>
</body>
</html>

首先浏览器从上到下依次解析文档构建DOM树,如下:

构建CSSOM树

CSSOM树全称为 CascadingStyleSheetsObjectModel层叠样式表对象模型,它与 DOM树的含义相差不大,只不过它是 CSS的对象集合。

浏览器获得外部 CSS文件的数据后,就会像构建 DOM树一样开始构建 CSSOM树,这个过程没有什么特别的差别。

css样式内容如下:

1
2
3
html, body { width: 100%; height: 100%; background-color: #fcfcfc;}
.title { font-size: 20px;}
.footer { font-size: 12px; color: #aaa;}

构建CSSOM树如图:

为了更直观的看见这个关键渲染路径的构建,我们可以使用 Chrome开发者工具中的Performance (即Timeline)功能来查看,下图是打开的是百度首页:

打开Event Log查看具体渲染过程

创建Render树

在构建了 DOM树和 CSSOM树之后,浏览器只是拥有了两个互相独立的对象集合, DOM树描述了文档的结构与内容, CSSOM树则描述了对文档应用的样式规则,想要渲染出页面,就需要将DOM树与CSSOM树结合在一起,构成渲染树

渲染树及其对应DOM树如图:

  • 图中渲染树viewport即视口,是文档的初始包含块,scroll代表滚动区域
  • 浏览器会先从 DOM树的根节点开始遍历每个可见节点(不可见的节点自然就没必要渲染到页面了,不可见的节点还包括被 CSS设置了 display:none属性的节点,值得注意的是 visibility:hidden属性并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,所以它会被渲染成一个空框)。
  • 对每个可见节点,找到其适配的 CSS样式规则并应用。
  • 渲染树构建完成,每个节点都是可见节点并且都含有其内容和对应规则的样式。

渲染树构建完毕后,浏览器得到了每个可见节点的内容与其样式,下一步工作则需要计算每个节点在窗口内的确切位置与大小,也就是布局阶段或者叫回流(reflow,relayout)

布局(Layout)或回流(reflow,relayout)

这个过程就是通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,将其安置在浏览器窗口的正确位置,而有些时候我们会在文档布局完成后对DOM进行修改,这时候可能需要重新进行布局,也可称其为回流,本质上还是一个布局的过程,每一个渲染对象都有一个布局或者回流方法,实现其布局或回流。

流(flow)

HTML采用的是基于流的方式定位布局,其按照从左到右,从上到下的顺序进行排列,详见CSS定位机制。

全局布局与局部布局

对渲染树的布局可以分为全局和局部的,全局即对整个渲染树进行重新布局,如当我们改变了窗口尺寸或方向或者是修改了根元素的尺寸或者字体大小等;而局部布局可以是对渲染树的某部分或某一个渲染对象进行重新布局。

脏位系统(dirty bit system)

大多数web应用对DOM的操作都是比较频繁,这意味着经常需要对DOM进行布局和回流,而如果仅仅是一些小改变,就触发整个渲染树的回流,这显然是不好的,为了避免这种情况,浏览器使用了脏位系统,只有一个渲染对象改变了或者某渲染对象及其子渲染对象脏位值为”dirty”时,说明需要回流。

表示需要布局的脏位值有两种:

  • “dirty”–自身改变,需要回流
  • “children are dirty”–子节点改变,需要回流

布局过程

布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,即对应着HTML文档根元素 ,然后下一级渲染对象,如对应着 元素,如此层层递归,依次计算每一个渲染对象的几何信息(位置和尺寸)

CSS采用了一种叫做盒子模型的思维模型来表示每个节点与其他元素之间的距离,它包括:边距,边框,填充,和实际内容,页面中的每个标签其实都是一个个盒子。布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小,所有相对的测量值也都会被转换为屏幕内的绝对像素值。

每一个渲染对象的布局流程基本如:

  • 1.计算此渲染对象的宽度(width);
  • 2.遍历此渲染对象的所有子级,依次:
  • 2.1设置子级渲染对象的坐标
  • 2.2判断是否需要触发子渲染对象的布局或回流方法,计算子渲染对象的高度(height)
  • 3.设置此渲染对象的高度:根据子渲染对象的累积高,margin和padding的高度设置其高度;
  • 4.设置此渲染对象脏位值为false。

强制回流
在渲染树布局完成后,再次操作文档,改变文档的内容或结构,或者元素定位时,会触发回流,即需要重新布局如:

  • DOM操作,如增加,删除,修改或移动;
  • 变更内容;
  • 激活伪类;
  • 访问或改变某些CSS属性(包括修改样式表或元素类名或使用JavaScript操作等方式);
  • 浏览器窗口变化(滚动或尺寸变化)

绘制(painting)
最后是绘制(paint)阶段或重绘(repaint)阶段,浏览器UI组件将遍历渲染树并调用渲染对象的绘制(paint)方法,将内容展现在屏幕上,也有可能在之后对DOM进行修改,需要重新绘制渲染对象,也就是重绘,绘制和重绘的关系可以参考布局和回流的关系。

全局与局部绘制
与布局相似,绘制也分为全局和局部绘制,即对整个渲染树或某些渲染对象进行绘制。

触发重绘
我们已经知道很多操作可能会触发回流,那么什么时候可能触发重绘呢,通常,当改变元素的视觉样式,如 background-color, visibility, margin, padding或 color时会触发全局或局部重绘。

现在我们来回顾下浏览器关键渲染路径的整个过程:

  • 处理 HTML标记数据并生成 DOM树。
  • 处理 CSS标记数据并生成 CSSOM树。
  • 将 DOM树与 CSSOM树合并在一起生成渲染树。
  • 遍历渲染树开始布局,计算每个节点的位置信息。
  • 将每个节点绘制到屏幕。

本篇文章我们讲了浏览器的渲染机制,理解这有助于我们更好的做性能优化,后续将讲解相关的性能优化,请关注.

参考资料

聊聊 JavaScript 与浏览器的那些事 - 引擎与线程
CSSOM 介绍
「译」JS 引擎核心: 原型优化

本文标题:浏览器渲染机制之一:基础概念

文章作者:zhyjor

发布时间:2017年11月24日 - 13:11

最后更新:2023年10月11日 - 02:10

原始链接:https://zhyjor.github.io/2017/11/24/浏览器渲染机制之一:基础概念/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

🐶 您的支持将鼓励我继续创作 🐶

热评文章