秋招复习计划-JavaScript知识点8

  |  

前言

浏览器的回流和重绘


36



浏览器的回流与重绘

想要了解浏览器的回流和重绘,我们首先要了解浏览器的渲染过程

浏览器的渲染过程

渲染过程

渲染过程:

  • 解析HTML生成DOM树,DOM树包含所有的标签,包括display: none的隐藏节点和JS动态添加的元素
  • 解析CSS生成CSSOM(样式结构体),在这个过程中会去掉浏览器不能识别的样式
  • 将DOM树和CSSOM结合生成渲染树renderTree,renderTree不包含隐藏的节点,但是visibility: hidden仍会被包含,因为他会影响布局
  • Layout(回流):也叫重排,根据生成的渲染树,进行回流,得到节点的几何信息(位置,大小)
  • Paintiing(重绘):根据渲染树和回流得到的集合信息,得到节点的绝对像素
  • Display:将像素发送给GPU,展示

CSSOM

样式表解析完毕后,系统会根据选择器将CSS规则添加到对应的哈希表中。这些哈希表包括:ID哈希表、类名称哈希表、标记名哈希表,还有通用哈希表。如果选择器是ID,规则会被添加到ID表中。

注意,这里CSSOM中的规则并不一定会全被引用

生成渲染树

renderTree

为了构建渲染树,浏览器主要完成以下工作:

  • 从DOM树的根节点开始遍历每个可见节点
  • 对于每个可见节点,从CSSOM中找到对应规则,并应用它们
  • 根据每个可见节点以及其对应的样式,组合生成渲染树

不可见的节点:

  • 一些不会渲染输出的节点,比如script、meta、link
  • 一些通过CSS隐藏的节点,比如说display:none。注意,利用visibility和opacity

回流(Layout)

回流是指通过构造渲染树,计算DOM节点在设备视口(viewport)内的确切位置和大小。这会导致渲染树的改变,也就是会重新构造renderTree,所以这个过程也称为重排

为了弄清每个对象在网站的确切大小和位置,浏览器从渲染树的根节点开始遍历,而在回流这个阶段,我们就需要根据视口具体的宽度,将其转换为实际的像素值。

每个页面至少需要一次回流,就是在页面第一次加载的时候

重绘(Painting)

当renderTree中的一些元素需要更新属性,而这些属性知识影响元素的外观,风格而不会影响布局,这个过程称为重绘

什么情况下会发生回流和重绘

不同条件下发生回流的范围及程度会不同,一下的操作都会导致回流:

  • 页面初始化渲染
  • 改变字体,改变元素尺寸(盒式模型的所有属性值的改变都会导致),各种情况有:
    • 设置style属性的值
    • 激活CSS伪类
    • 操作class属性
  • 改变元素内容(文本或图片等,例如在input框中输入文字)
  • 添加或删除可见的DOM元素(删除本身就是不可见的元素不会发生回流)
  • fixed定位的元素,在拖动滚动条时会一直回流
  • 调整窗口大小
  • 计算offsetWIdth和offsetHeight属性

对以下属性

offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。

的操作,浏览器并不立即执行,而是会先会存在队列中,有一定时间顺序的去执行这些操作,但是在这个过程中我们需要去获取在该队列中的属性是,浏览器为了取得正确的值就会触发回流。这回影响到浏览器优化,所以在多次使用这些值时需要进行缓存。

需要注意的是,回流必将引起重绘,而重回不一定会回流。回流的成本开销要高于重绘,一个节点的回流往往会导致其子节点以及同级节点的回流。

在CSS3标准下,什么元素的改变会导致浏览器的回流和重绘,可以查看https://csstriggers.com/ 。这个网站包含了CSS3标准下的哪些属性在不同浏览器内核的情况下会导致回流或者重绘。

如何减少回流和重绘

由于回流和重绘会消耗额外的计算资源,所以我们在编写页面时需要尽量减少回流和重绘的触发,从而达到页面的优化。

cssText

当我们使用JS去修改一个元素的CSS样式时,我们需要尽量注意我们的方式,譬如

1
2
3
4
let s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; //回流+重绘
s.margin = "2px"; // 回流+重绘

这里元素的集合信息被修改了三次,由于浏览器的优化机制,这些修改会被缓存起来,但是如果这期间有特定的方法或属性访问位置信息的话就会触发三次回流加重绘。所以最好还是通过cssText一次性修改CSS样式

1
el.style.cssText += 'borde: 1px solid red; padding: 2px; margin: 2px'

修改class名

利用修改class名来代替JS对CSS样式的操作。例如对于按钮我们有两两套样式,点击前和点击后,那么我们可以在style标签内写入两套样式,然后在按钮点击后,替换按钮的class名,应用点击后的样式

1
el.class = "click"

这个方法的优化原理和cssText是一样的,及将对样式的修改一次性完成。

display:none

隐藏元素,应用修改,确定以后重新显示

1
2
3
4
el.style.display = 'none';
el.style.padding = '2px';
el.style.margin = '2px';
el.style.display = 'block'

还是一样的,先让元素不可见,这样对它的所有操作都不会触发回流和重绘,等所有的修改确定后再重新显示,但是我总觉得这种方法怪怪的。

cloneNode

将原始元素拷贝到一个脱离文档的系欸但中,修改副本,完成后再进行替换,其实就是避免对原生DOM树的操作,大体来说就是虚拟DOM的理念

1
2
3
4
let old = document.getElementById('list');
let clone = old.cloneNode(true);
// 对clone进行一些操作
old.parentNode.replaceChild(clone,old);

document.createDocumentFragment

使用文档片段在当前DOM之外建立一个子树,再将其插入回去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var fragment = document.createDocumentFragment();
var list = document.getElementById("list");
for (var i = 0; i < 10; i++)
{
    var _li = document.createElement("li");
    _li.onmouseover = function ()
    {
        this.style.backgroundColor = "#22b909";
        this.style.width = "120px";
        this.style.height = "50px";
    }
    _li.onmouseout = function ()
    {
        this.style.backgroundColor = "";
        this.style.width = "100px";
        this.style.height = "40px";
    }
    fragment.appendChild(_li);
}
list.appendChild(fragment);

其它的优化方式

  • 使用translate代替top改变,top会触发回流而translate不会
  • 使用opacity替代visibility,前者回流和重绘都不会触发,而后者都会
  • 不要使用table布局,table的一个小改动都有可能触发回流,尽量使用div
  • 启动GPU硬件加速,使用transform:translateZ(0)、transform:tanslate3d(0,0,0)来启动GPU硬件加速

CSS与JS对DOM解析和渲染的阻塞

通过\

文章目录
  1. 1. 浏览器的回流与重绘
    1. 1.1. 浏览器的渲染过程
    2. 1.2. 回流(Layout)
    3. 1.3. 重绘(Painting)
    4. 1.4. 什么情况下会发生回流和重绘
    5. 1.5. 如何减少回流和重绘
    6. 1.6. CSS与JS对DOM解析和渲染的阻塞
|