06.游览器渲染过程

浏览器要渲染页面,需要我们告诉它要渲染哪个页面,因此我们需要输入地址,然后浏览器向服务器请求资源,服务器把资源给到浏览器后,浏览器开始页面的渲染,所以我们从以下 4 个方面说明浏览器的渲染过程

  • 用户输入 URL 地址

  • 浏览器从 URL 中解析出域名

  • 将域名转化为 ip; 浏览器先查找本地DNS缓存列表,没有的话再向浏览器默认的DNS服务器发送查询请求同时缓存 DNS解析的过程是什么

  • 从 URL 中解析出端口

  • 根据 ip 和 端口就可以定位到资源

浏览器建立与目标服务器的 TCP 连接(通过三次握手)

浏览器向服务器发送一条 HTTP 请求报文

服务器向浏览器返回一条 HTTP 响应报文 服务器端收到请求后的由web服务器(准确说应该是http服务器)处理请求,诸如Apache、Ngnix、IIS 等。

web服务器解析用户请求,知道了需要调度哪些资源文件,再通过相应的这些资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过web服务器返回给浏览器客户端。

关闭连接 为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可以发起关闭请求。与创建TCP连接的3次握手类似,关闭TCP连接,需要4次握手。

关键渲染路径(Critical Rendering Path)

关键渲染路径是指浏览器从最初接收请求来的HTML、CSS、javascript等资源,然后解析、构建树、渲染布局、绘制,最后呈现给客户能看到的界面这整个过程。

所以浏览器的渲染过程主要包括以下几步:

解析HTML生成DOM树。

解析CSS生成CSSOM规则树。

将DOM树与CSSOM规则树合并在一起生成渲染树。

遍历渲染树开始布局,计算每个节点的位置大小信息。

将渲染树每个节点绘制到屏幕。

构建DOM树

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,生成DOM树。

需要注意的是,DOM树的生成过程中可能会被CSS和JS的加载执行阻塞。

构建CSSOM规则树

浏览器解析CSS文件并生成CSS规则树,每个CSS文件都被分析成一个StyleSheet对象,每个对象都包含CSS规则。CSS规则对象包含对应于CSS语法的选择器和声明对象以及其他对象。

构建渲染树

通过DOM树和CSS规则树我们便可以构建渲染树。浏览器会先从DOM树的根节点开始遍历每个可见节点。 对每个可见节点,找到其适配的CSS样式规则并应用。

渲染树构建完成后,每个节点都是可见节点并且都含有其内容和对应规则的样式。这也是渲染树与DOM树的最大区别所在。渲染树是用于显示,那些不可见的元素当然就不会在这棵树中出现了,譬如。除此之外,display等于none的也不会被显示在这棵树里头,但是visibility等于hidden的元素是会显示在这棵树里头的。

Render Tree 的构建其实就是 DOM Tree 和 CSSOM Attach 的过程。 呈现器是和 DOM 元素相对应的,但并非一一对应。Render Tree 实际上就是一个计算好样式,与HTML对应的(包括哪些显示,那些不显示)的Tree。

渲染树布局(Layout)

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

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

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

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

“dirty”–自身改变,需要回流

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

布局是一个从上到下,从外到内进行的递归过程,从根渲染对象,即对应着HTML文档根元素,然后下一级渲染对象,如对应着元素,如此层层递归,依次计算每一个渲染对象的几何信息(位置和尺寸)。

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

计算此渲染对象的宽度(width);

遍历此渲染对象的所有子级,依次:

2.1 设置子级渲染对象的坐标

2.2判断是否需要触发子渲染对象的布局或回流方法,计算子渲染对象的高度(height)

设置此渲染对象的高度:根据子渲染对象的累积高,margin和padding的高度设置其高度;

设置此渲染对象脏位值为false。

渲染树绘制(Painting)

在绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。绘制工作是使用用户界面基础组件完成的。

CSS2 规范定义了绘制流程的顺序。绘制的顺序其实就是元素进入堆栈样式上下文的顺序。这些堆栈会从后往前绘制,因此这样的顺序会影响绘制。块呈现器的堆栈顺序如下:

1.背景颜色

2.背景图片

3.边框

4.子代

5.轮廓

回流与重绘

根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML 默认是流式布局的,CSS 和 js 会打破这种布局,改变 DOM 的外观样式以及大小和位置。这时就要提到两个重要概念:replaint 和 reflow。

replaint:屏幕的一部分重画,不影响整体布局,比如某个 CSS 的背景色变了,但元素的几何尺寸和位置不变。

reflow: 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是 Reflow,或是 Layout。

所以我们应该尽量减少 reflow 和 replaint,我想这也是为什么现在很少有用 table 布局的原因之一。

display: none 会触发 reflow,visibility: hidden 属性并不算是不可见属性,它的语义是隐藏元素,但元素仍然占据着布局空间,它会被渲染成一个空框,所以visibility:hidden 只会触发 repaint,因为没有发生位置变化。

有些情况下,比如修改了元素的样式,浏览器并不会立刻 reflow 或 repaint 一次,而是会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。有些情况下,比如 resize 窗口,改变了页面默认的字体等。对于这些操作,浏览器会马上进行 reflow。

引发回流与重绘的原因

initial。网页初始化的时候。

Incremental。一些Javascript在操作DOM Tree时。

Resize。其些元件的尺寸变了。

StyleChange。如果CSS的属性发生变化了。

Dirty。几个Incremental的reflow发生在同一个frame的子树上。

如何减少回流、重绘

使用 transform 替代 top

使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)

不要把节点的属性值放在一个循环里当成循环里的变量。

for (let i = 0; i < 1000; i++) {
// 获取 offsetTop 会导致回流,因为需要去获取正确的值
    console.log(document.querySelector('.test').style.offsetTop)
}

不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局

动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame

CSS 选择符从右往左匹配查找,避免节点层级过多

将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。

渲染阻塞

当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行,然后继续构建DOM。每次去执行JavaScript脚本都会严重地阻塞DOM树的构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM还没有下载和构建,浏览器甚至会延迟脚本执行和构建DOM,直至完成其CSSOM的下载和构建。

defer 与 async

都不会阻塞 DOM 解析

defer 会在 DomContentLoader 前依次执行

async 下载完立即执行,且顺序无关

为什么操作 DOM 慢

因为 DOM 是属于渲染引擎中的东西,而 JS 又是 JS 引擎中的东西。当我们通过 JS 操作 DOM 的时候,其实这个操作涉及到了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作 DOM 次数一多,也就等同于一直在进行线程之间的通信,并且操作 DOM 可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

渲染页面时常见哪些不良现象

由于浏览器的渲染机制不同,在渲染页面时会出现两种常见的不良现象—-白屏问题和FOUS(无样式内容闪烁)

FOUC:由于浏览器渲染机制(比如firefox),再CSS加载之前,先呈现了HTML,就会导致展示出无样式内容,然后样式突然呈现的现象;

白屏:有些浏览器渲染机制(比如chrome)要先构建DOM树和CSSOM树,构建完成后再进行渲染,如果CSS部分放在HTML尾部,由于CSS未加载完成,浏览器迟迟未渲染,从而导致白屏;也可能是把js文件放在头部,脚本会阻塞后面内容的呈现,脚本会阻塞其后组件的下载,出现白屏问题。

浏览器工作流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。

CSSOM会阻塞渲染,只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。

通常情况下DOM和CSSOM是并行构建的,但是当浏览器遇到一个script标签时,DOM构建将暂停,直至脚本完成执行。但由于JavaScript可以修改CSSOM,所以需要等CSSOM构建完毕后再执行JS。

如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,建议将 script 标签放在 body 标签底部。