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

吐槽君 分类:javascript

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

渲染流程概述

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

网页加载过程

1. 加载HTML-DOM结构
2. CSS和JS
3. 图片和多媒体
4. 加载事件触发
注意:
a. 在浏览器还没有完全接收 HTML 文件时便开始渲染、显示网页;
b. 在执行 HTML 中代码时,根据需要,浏览器会继续请求图片、CSS、JavsScript等文件,过程同请求 HTML 
 

渲染过程主要分为五步

1. 构建DOM树:浏览器将获取的HTML文档解析成DOM树
2. 构建CSSOM规则树:处理CSS标记,构成层叠样式表模型CSSOM(CSS Object Model)
3. 构建渲染树:将DOM和CSSOM合并为渲染树(Render Tree),代表一系列将被渲染的对象
[有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系]
4. 渲染树布局:计算出每个节点在屏幕中的大小及位置
5. 渲染树绘制:将渲染树的各个节点绘制到屏幕上
 

渲染具体过程

1. 构建DOM树

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

<html> <!-- HTML文档 -->
    <head>
        <meta name="description" content="浏览器如何渲染网页">
        <link rel="stylesheet" href="./css">
    </head>
    <body>
        <p>Hello,</p>
        <span>web performance</span>
        <p>students</p>
        <img src="..."></img>
    </body>
</html>
 

image.png

过程分析
  1. Bytes:当服务器返回一个HTML文件给浏览器时,浏览器接受到的是【一些字节数据】
  2. Characters:浏览器根据HTTP响应中的编码方式(通常是utf-8),解析字节数据,【得到一些字符】
  3. Tokens:浏览器根据DTD中的对元素的定义,对接受到的字符进行【语义化】(tokens)
  4. Nodes:浏览器使用这些语义块(tokens)创建对象(Nodes)
  5. DOM:HTML解析器构建当前节点的所有子节点,再去构建当前节点的下一个兄弟节点
1. 当这些节点是普通节点的话,HTML解析器就会将这些节点加入到DOM树中
2. 当这些节点是JS代码的话,HTML解析器就会将控制权交给JS解析器
3. 如果这些节点是CSS代码的话,HTML解析器就会将控制权交给CSS解析器
 
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 文本时,会执行一个转换操作,将 CSS 文本转换为浏览器可以理解的styleSheets

image.png

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

注意
1. CSS解析可以与DOM解析【同时进行】
2. CSS解析与script的【执行互斥】
3. 在Webkit内核中进行了script执行优化,【只有在JS访问CSS时】才会发生互斥
 
分析
1. CSS解析本身不会阻塞DOM解析
2. 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的外观样式以及大小和位置】。因此我们就需要知道两个概念:repaint和reflow

重绘 repaint

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

回流 reflow

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

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

触发重绘的原因

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

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

触发回流的原因

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

减少reflow、repaint触发次数

CSS方面

  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属性设为absolute或fixed(比如动画效果)
position属性设为absolute或fixed意味着脱离文档流,因而元素样式发生改变时不会引起回流重绘
 

JS方面

  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 节点操作

如何优化渲染效率

  1. 如何解决阻塞问题
  2. 如何减少重绘回流(2+4)

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

回复

我来回复
  • 暂无回复内容