阅读 121

浏览器系列 -- 渲染原理及过程

从输入 URL 到页面加载的全过程参考 计算机网络系列 -- 从URL到网页加载

这里主要讲解页面渲染部分的过程

渲染流程概述

浏览器从最初接收请求来的HTML、CSS、javascript等`二进制形式`的资源,然后`解析、构建树、渲染布局、绘制`
最后呈现给用户能看到的界面的整个过程
复制代码

渲染引擎

一个渲染引擎主要包括:HTML解析器,CSS解析器,javascript引擎,布局layout模块,绘图模块

  • HTML解析器:解释HTML文档的解析器,主要作用是将HTML文本解释成DOM树。
  • CSS解析器:它的作用是为DOM中的各个元素对象计算出样式信息,为布局提供基础设施
  • Javascript引擎:使用Javascript代码可以修改网页的内容,也能修改css的信息,javascript引擎能够解释javascript代码,并通过DOM接口和CSS树接口来修改网页内容和样式信息,从而改变渲染的结果。
  • 布局(layout):在DOM创建之后,Webkit需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的内部表示模型
  • 绘图模块(paint):使用图形库将布局计算后的各个网页的节点绘制成图像结果

渲染过程

浏览器渲染页面的整个过程:浏览器会从上到下解析文档

  1. 遇见 HTML 标记,调用HTML解析器解析为对应的 token 。
  2. 遇见 style/link 标记,调用(可能是 HTML/CSS 解析器)解析器,处理 CSS 标记并构建 CSSOM 树。
  3. 遇见 script 标记,调用 JS 引擎 处理script标记,绑定事件、修改DOM树/CSS树 等
1. 当这些节点是`普通节点`的话,HTML解析器就会将这些节点`加入到DOM树中`
2. 当这些节点是`JS代码`的话,HTML解析器就会将控制权`交给JS引擎`
3. 如果这些节点是`CSS代码`的话,HTML解析器就会将控制权`交给CSS解析器`
复制代码
  1. 将 DOM树 与 CSS树 合并成一个render 树(渲染树),代表一系列将被渲染的对象
[有了Render Tree,浏览器知道了网页中有哪些节点、各个节点的CSS定义以及他们的从属关系]
复制代码
  1. 根据渲染树来渲染,以计算每个节点的几何信息(这一过程需要依赖图形库)。
  2. 将各个节点绘制到屏幕上
注意:
a. 在浏览器还没有完全接收 HTML 文件时便开始渲染、显示网页;`边接收边渲染`
b. 在执行 HTML 中代码时,根据需要,浏览器会`继续请求`图片、CSS、JavsScript等文件,过程同请求 HTML
复制代码

渲染具体过程

1. 构建DOM树

当浏览器接收到服务器响应来的HTML文档后,会遍历文档节点,HTML解析器开始工作,构建DOM树

image.png

浏览器接收到服务器响应来的HTML文档是二进制字节流的形式

image.png

过程分析
  1. Bytes:当服务器返回一个HTML文件给浏览器时,浏览器接受到的是一些字节数据
  2. Characters:浏览器根据HTTP响应中的编码方式(通常是utf-8),解析字节数据,得到HTML文档
  3. Tokens:浏览器根据DTD中的对元素的定义,对接受到的字符进行语义化(tokens)
  4. Nodes:浏览器使用这些语义块(tokens)创建对象(Nodes)
  5. DOM:HTML解析器构建当前节点的所有子节点,再去构建当前节点的下一个兄弟节点
1.由于CSS解析器、JS解析器的优先级大,所以DOM树会暂时停止构建,因而造成`白屏`,就是所谓`浏览器阻塞`
2.不过,当外联的JS代码和CSS代码还没从服务器传到浏览器的时候,如果此时`DOM树上有可视元素`的话
浏览器通常会将一些内容`提前渲染`到屏幕上来
复制代码

阻塞渲染问题参考 浏览器系列 -- 阻塞渲染

  1. 当HTML解析器读到最后一个节点的时候,整个DOM树也构建完成了,这个时候就会触发【domContentloaded】事件。至此,DOM树就全部构建完成了。
1. `domContentloaded`事件触发时:仅当DOM加载完成,`不包括样式表,图片等`
2. `load`事件触发时:页面上`所有的DOM、样式表、脚本、图片都已加载完成`
复制代码
注意
1. DOM树在构建的过程中可能会因为`CSS和JS的加载``被阻塞`
2. `display:none`的元素也会在DOM树中
3. `注释`也会在DOM树中
4. `script标签`会在DOM树中
复制代码

2. 构建CSSOM规则树

当获取style标签内的css、或者内嵌的css,浏览器会发送请求获得该标签中标记的CSS

当渲染引擎接收到 CSS 二进制文本后,会经过一系列的转换,最终变成浏览器可以理解的styleSheets

image.png

CSSOM规则树的构建也是要经过 Bytes→characters→tokens→nodes→object model 的过程

注意
1. CSS解析可以与DOM解析`同时进行`
2. CSS解析与script的`执行互斥`
3. 在Webkit内核中进行了script执行优化,`只有在JS访问CSS时`才会发生互斥
复制代码
分析
2. CSSOM规则未构建好`会阻塞`渲染树的构建(即CSS阻塞渲染)
3. JS代码的执行会阻塞DOM解析,而如果JS代码访问了CSS,则CSS解析优先于JS代码优先于DOM树的构建
(联系到重绘、回流和浏览器阻塞渲染)
复制代码

阻塞渲染问题参考 浏览器系列 -- 阻塞渲染

3. 构建渲染树

浏览器会先从DOM树的根节点开始遍历每个可见节点,然后对每个可见节点找到适配的CSS样式规则并应用

image.png

注意:渲染树和DOM 树【不完全对应】
因为
1. 【display: none】【meta】【script】【link】的元素【不在】Render Tree中
2. 但【visibility: hidden】的元素【在】Render Tree中
复制代码

渲染树生成后,还是没有办法渲染到屏幕上,渲染到屏幕需要得到各个节点的位置信息,这就需要布局(Layout)的处理了

4. 渲染树布局(layout of the render tree)

根据生成的渲染树,从渲染树的根节点开始遍历,计算每个节点的几何信息(在设备视口内的确切位置大小)

布局阶段的输出就是常说的盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小

注意
1. 【float元素】,【absoulte元素】,【fixed元素】会发生位置偏移。
2. 【脱离文档流】,其实就是【脱离Render Tree】
复制代码

5. 渲染树绘制(Painting the render tree)

经过生成的渲染树和Layout阶段,得到了所有可见节点具体的几何信息与样式,然后将渲染树的每个节点转换成【屏幕上的实际像素】。渲染树的绘制工作是【由浏览器的UI后端组件】完成的

重绘与回流

我们都知道HTML默认是流式布局的,但CSS和JS会打破这种布局,尤其是用户与网页交互时,【改变DOM的外观样式以及大小和位置】。因此我们就需要知道两个概念:repaintreflow

重绘 repaint (操作一个DOM节点不会影响其他节点)

在布局过程中,遇到style属性或js操作,改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,被改变元素本身要重画,但是元素的几何尺寸、元素以及其他元素的位置没有发生改变

回流 reflow (操作一个DOM节点会影响其他节点)

布局过程中,遇到style属性或js操作,对元素的大小、定位等有改变,需要重新计算和绘制所有的结点几何尺寸和位置,则会引起回流。

值得注意的是:比如修改了元素的样式,浏览器【并不会立刻】reflow或repaint一次
因为有【flush队列】让多次回流重绘【变成一次】回流重绘
复制代码

触发重绘的原因

除了元素的尺寸和位置变化的变化:

背景色、边框颜色、文字颜色、字体改变

触发回流的原因

  1. 页面第一次渲染(初始化)
  2. DOM树变化(如:增删节点)
  3. padding、margin改变
  4. 元素的尺寸大小、包括文字大小改变
  5. 浏览器窗口resize
  6. 获取元素的某些属性

减少reflow、repaint触发次数

CSS方面 —— (多半是style属性引起的:把样式放在style标签或link标签)

  1. 避免设置多层内联样式,将样式合并在一个外部类
// 内联样式
<div style="width: 65px;height: 20px;border: 1px solid;">元素</div>
// 外部类
<head>
    <meta charset="utf-8" />
    <style type="text/css">
        div {
            width: 65px;
            height: 20px;
            border: 1px solid;
        }
    </style>
</head>
复制代码
  1. 将需要多次回流的元素position属性设为absolutefixed(比如动画效果)
position属性设为absolute或fixed意味着脱离文档流,因而元素样式发生改变时不会引起回流重绘
复制代码

JS方面 —— (操作DOM节点时引起的:重点减少次数)

  1. 避免多次读取 DOM 节点可以用变量将结果存起来
  2. 需要多次操作同一 DOM 节点时可以先将该节点的元素设置display:none,操作完再显示。(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
  3. 操作多个 DOM 节点时将所有的 DOM 操作集中在一起(原因如下)
a. 浏览器会维护1`队列`,把所有会引起回流、重绘的操作放入这个队列
b. 等队列中的操作到了`一定的数量`或者到了`一定的时间间隔`,浏览器就会`flush队列`,进行一个批处理
c. 这样就会让多次的回流、重绘`变成一次`回流重绘
d. 有特殊情况,会`提前执行`flush队列
需要获取这些值时:
width、height;
clientWidth、clientHeight、clientTop、clientLeft;(clientWidth = width+左右padding)
offsetWidth、offsetHeight、offsetTop、offsetLeft;(offsetWidth = width+左右padding+左右boder)
scrollWidth、scrollHeight、scrollTop、scrollLeft;
复制代码

4.形变和位移用transfrom代替 DOM 节点操作

附加问题

display:none和visibility: hidden的区别

相同点:控制元素的显示与隐藏

不同点:

  1. 意义上:display:none表示元素不要了,visibility: hidden表示元素看不见
  2. 原理上:display:none的元素不在渲染树上,visibility: hidden的元素在渲染树上
注意:display:none的元素虽然不在渲染树上,但会在 DOM 树上
复制代码
  1. 影响上:设置元素为display:none时会引起回流,设置元素为visibility: hidden会引起重绘
注意:display:none的元素是操作 DOM 节点,使得该元素所在节点去掉,所以需要回流
复制代码

回流 reflow 和重绘 repaint 的联系与区别

联系:回流 reflow 必定引起重绘 repaint

区别:

  1. 本质上
  2. 触发原因上
  3. 过程上

如何减少reflow、repaint触发次数

CSS 方面
  1. 外部类代替多个内联样式
  2. 设置position脱离文档流
JS 方面
  1. 多次读取 DOM 节点时变量来存
  2. 多次操作同一 DOM 节点时先设置display:none
  3. 同时操作多个 DOM 节点时集中,有flush队列
  4. 形变和位移用transfrom代替 DOM 节点操作

如何优化渲染效率

优化方案见 浏览器系列 -- 优化页面加载

文章分类
前端
文章标签