浏览器渲染机制

首先,浏览器的渲染机制中有几个基本概念名称:

(1)、DOM:浏览器将 HTML 解析成树形的数据结构

(2)、CSSOM:浏览器将 CSS 解析成树形的数据结构

(3)、Render Tree:DOM 和 CSSOM 合并后生成 Render Tree(渲染树)

(4)、Layout:计算出 Render Tree 每个节点的具***置

(5)、Painting:通过显卡,将 Layout 后的节点内容分别呈现到屏幕上

浏览器整个渲染流程如下:

(1)、用户输入一个 URL 后,浏览器就会向服务器发出一个请求,请求 URL 对应的资源

(2)、接到服务器的响应内容后,浏览器的 HTML 解析器,会将 HTML 文件解析成一颗 DOM 树,DOM 树的构建是一个深度遍历的过程,当前节点的所有子节点都构建完成后,才会去构建当前节点的下一个兄弟节点

(3)、将 CSS 解析成 CSSOM 树

(4)、根据 DOM 树与 CSSOM 树,构建 Render Tree

(5)、浏览器会根据 Render Tree 能知道网页中哪些有节点,各个节点的 CSS,以及各个节点的从属关系

(6)、计算出每个节点在屏幕中的位置后,最后一步就是 Painting,根据计算出的规则,把内容画到屏幕上

注意:

浏览器在获得 HTML 文件后,是自上而下的加载,并在加载过程中进行解析与渲染

加载过程中:

遇到外部 CSS 文件和图片等静态资源时,浏览器会另外发送一个异步请求

遇到 js 文件时,HTML 文件会挂起渲染的进程,等待 js 文件加载完毕后,再继续进行渲染,因为 js 可能会修改 DOM,导致后续 HTML 资源白白加载,这也是为什么建议将 js 文件写在底部 body 标签前

请问你了解什么是重绘?什么是回流?两者有何区别?

重绘:

更换某个元素颜色,这样的行为是不影响页面布局,DOM 树不会变化,但颜色变了,使该元素所在的 Layer 重新渲染

常见情况:

(1)、回流必定引发重绘,但重绘也会单独触发

(2)、背景色、颜色、字体改变(字体大小改变,会触发回流)

回流:

增删 DOM 节点,或修改一个元素的宽高,页面布局发生变化,DOM 树结构发生变化,需要重新构建 DOM 树,而 DOM 树与渲染树是紧密相连的,DOM 树构建完,渲染树也会随之对页面进行再次渲染

常见情况:

(1)、页面渲染初始化

(2)、DOM 树变化(添加或者删除可见的 DOM 元素、元素位置改变)

(3)、Render 树变化(元素尺寸改变:边距、填充、边框、宽度和高度)

(4)、浏览器窗口尺寸改变,resize 事件发生

(5)、内容改变:文本改变或者图片大小改变而引起的计算值宽度和高度改变;

(6)、查询布局信息,包括 offestLeft/Top/Width/Height、scrollLeft/Top/Width/Height、clientLeft/Top/Width/Height、浏览为了返回最新值,会触发回流

请问在浏览器中输入 URL 后,浏览器会做哪些工作?

从输入 URL 到渲染出整个页面包括三个部分:

(1)、DNS 解析 URL

DNS 解析就是寻找哪个服务器上有请求的资源,因为 ip 地址不易记忆,一般会使用 URL 域名(如www.baidu.com)作为网址,DNS解析就是将域名“翻译”成IP地址

具体过程:

a、浏览器缓存:浏览器会按照一定的频率,缓存 DNS 记录

b、操作系统缓存:如果浏览器缓存中找不到需要的 DNS 记录,就会取操作系统中找

c、路由缓存:路由器也有 DNS 缓存

d、ISP 的 DNS 服务器:ISP 有专门的 DNS 服务器应对 DNS 查询请求

e、根服务器:ISP 的 DNS 服务器找不到,就要向根服务器发出请求,进行递归查询

(2)、浏览器发送请求与服务器交互

a、浏览器利用 tcp 协议通过三次握手与服务器建立连接

http 请求包括 header 和 body,header 中包括请求的方式(get 和 post)、请求的协议 (http、https、ftp)、请求的地址 ip、缓存 cookie,body 中有请求的内容

b、浏览器根据解析到的 IP 地址和端口号发起 http 的 get 请求

c、服务器接收到 http 请求之后,开始搜索 html 页面,并使用 http 返回响应报文

d、若状态码为 200 显示响应成功,浏览器接收到返回的 HTML 页面后,开始渲染页面

(3)、浏览器对接收到的 HTML 页面进行渲染

a、浏览器根据深度遍历的方式把 HTML 节点遍历成 DOM 树

b、将 CSS 解析成 CSSOM 树

c、将 DOM 树和 CSSOM 树构造成 Render 树

d、根据 Render 树计算所有节点在屏幕中的位置,进行布局(回流)

e、遍历 Render 树并调用硬件 API 绘制所有节点(重绘)

所以以下几个动作可能会导致性能问题:

  • 改变 window 大小
  • 改变字体
  • 添加或删除样式
  • 文字改变
  • 定位或者浮动
  • 盒模型

渲染

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

  • PARSE:解析 HTML,构建 DOM 树。

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

  • STYLE:为每个节点计算最终的有效样式(CSS-CSSOM)。

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

  • LAYOUT:为每个节点计算位置和大小等布局信息。

    Layout 树和 DOM 树不一定是一一对应的,为了构建 Layout 树,浏览器主要完成了下列工作:从 DOM 树的根节点开始遍历每个可见节点。某些不可见节点(例如 script、head、meta 等),它们不会体现在渲染输出中,会被忽略。某些通过设置 display 为 none 隐藏的节点,在渲染树中也会被忽略。为伪元素创建 LayoutObject。为行内元素创建匿名包含块对应的 LayoutObject。对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们。产出可见节点,包含其内容和计算的样式。

  • PAINT:绘制不同的盒子,为了避免不必要的重绘,将会分成多个层进行处理。

    DOM 树的 Document 节点对应的 RenderView 节点。DOM 树中 Document 节点的子节点,也就是 HTML 节点对应的 RenderBlock 节点。显式指定 CSS 位置的节点(position 为 absolute 或者 fixed)。具有透明效果的节点。具有 CSS 3D 属性的节点。使用 Canvas 元素或者 Video 元素的节点。浏览器遍历 LayoutObject 树的时候,建立了 PaintLayer 树,LayoutObject 与 PaintLayer 也不一定是一一对应的。每个 LayoutObject 要么与自己的 PaintLayer 关联,要么与拥有 PaintLayer 的第一个祖先的 PaintLayer 关联。

  • COMPOSITE & RENDER:将上述不同的层合成为一张位图,发送给 GPU,渲染到屏幕上。为了提高浏览器的渲染性能,通常的手段是保证渲染流程不被阻塞,避免不必要的绘制计算和重排重绘,利用 GPU 硬件加速等技术来提高渲染性能。

次级资源加载

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

CSSOM

CSSOM 和 DOM 是并行构建的,构建 CSSOM 不会阻塞 DOM 的构建。但 CSSOM 会阻塞 JS 的执行,因为 JS 可能会操作样式信息。虽然 CSSOM 不会阻塞 DOM 的构建,但在进入下一阶段之前,必须等待 CSSOM 构建完成。这也是通常所说的 CSSOM 会阻塞渲染。

浏览器渲染性能的优化

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

2.1 减少渲染中的重排重绘

使用合适的网页分层技术:如使用多层 canvas,将动画背景,运动主体,次要物体分层,这样每一帧需要变化的就只是一个或部分合成层,而不是整个页面。
使用 CSS Transforms 和 Animations:它可以让浏览器仅仅使用合成器来合成所有的层就可以达到动画效果,而不需要重新计算布局,重新绘制图形。CSS Triggers 中仅触发 Composite 的属性就是最优的选择。

2.2 优化影响渲染的资源在浏览器解析

HTML 的过程中,CSS 和 JS 都有可能对页面的渲染造成影响。

优化方法包括以下几点:
关键 CSS 资源放在头部加载。
JS 通常放在页面底部。
为 JS 添加 async 和 defer 属性。
body 中尽量不要出现 CSS 和 JS。
为 img 指定宽高,避免图像加载完成后触发重排。
避免使用 table, iframe 等慢元素。原因是 table 会等到它的 dom 树全部生成后再一次性插入页面中;iframe 内资源的下载过程会阻塞父页面静态资源的下载及 css, dom 树的解析。

CSSOM

像 和它的子节点以及任何具有 display: none 样式的结点,例如 script { display: none; }(在 user agent stylesheets 可以看到这个样式)这些标签将不会显示,也就是它们不会出现在 Render 树上。具有 visibility: hidden 的节点会出现在 Render 树上,因为它们会占用空间。由于我们没有给出任何指令来覆盖用户代理的默认值,因此上面代码示例中的 script 节点将不会包含在 Render 树中。

每个可见节点都应用了其 CSSOM 规则。Render 树保存所有具有内容和计算样式的可见节点——将所有相关样式匹配到 DOM 树中的每个可见节点,并根据 CSS 级联确定每个节点的计算样式。

第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。

总结

综上所述,我们得出这样的结论:

浏览器工作流程:构建 DOM -> 构建 CSSOM -> 构建渲染树 -> 布局 -> 绘制。
CSSOM 会阻塞渲染,只有当 CSSOM 构建完毕后才会进入下一个阶段构建渲染树。
通常情况下 DOM 和 CSSOM 是并行构建的,但是当浏览器遇到一个不带 defer 或 async 属性的 script 标签时,DOM 构建将暂停,如果此时又恰巧浏览器尚未完成 CSSOM 的下载和构建,由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 构建完毕后再执行 JS,最后才重新 DOM 构建。
()