回流(重排)与重绘 —— 什么是回流和重绘,造成的原因是什么,如何去减少?

Posted Kabukiyo Lin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回流(重排)与重绘 —— 什么是回流和重绘,造成的原因是什么,如何去减少?相关的知识,希望对你有一定的参考价值。

浏览器的渲染原理

浏览器的渲染原理步骤:

  • 解析文档(html、SVG、XHTML),生成 DOM Tree
  • 解析CSS,生成 CSSOM Rule Tree
  • 根据 DOM Tree 和 CSSOM Rule 生成 Rendering Tree
  • 布局
  • 绘制

具体参考:

浏览器渲染原理、浏览器的解析

“回流 / 重排”和“重绘”的定义

①、回流的定义 :当Rendering Tree 中部分元素的尺寸大小、布局、隐藏等属性改变时,浏览器的布局需要调整,则需要重新渲染DOM。这个过程就叫回流。回流也叫重排(对整个页面进行重新排版)。

②、重绘的定义:当元素属性的改变不影响DOM Tree的结构,即不会影响浏览器的布局,只是“表象”发生变化(如background-color,visibility等),那么针对新样式对元素进行重新绘制

③、二者的联系

  • 回流一定会引起重绘,重绘不一定引起回流。
  • 回流需要重新渲染DOM,也要重新处理Rendering Tree。即回流的开销比重绘要大。

何时发生回流

比较好理解的,DOM元素发生变化:

  • 添加或删除可见元素
  • 元素的位置改变
  • 元素的尺寸大小改变(width,height,padding,border,margin等)

还有一类情况,即访问某些属性或调用某些方法或resize事件发生时

属性

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight

方法:

  • getComputedStyle()
  • getBoundingClientRect()
  • currentStyle()

事件:

  • 浏览器窗口尺寸改变,即resize事件
  • 页面初始化渲染

访问这些属性或调用这些方法,都是关乎元素在浏览器中的坐标位置,为了能够获得精确的值,浏览器需要进行即时计算

嗯哼?即时计算?那就是要回流了。因为回流就是要计算元素布局位置构建DOM。

也就是说:当访问这些属性,或者调用这些方法,为了即时获得准确结果,浏览器干脆就重新计算元素位置大小,即发生一次回流!所以访问这些属性,调用这些方法,也会引起回流!

另外,resize事件,让浏览器窗口尺寸发生变化。而浏览器正是根据浏览器窗口大小来计算元素的位置和大小。那么浏览器的窗口大小发生变化,浏览器也需要重新去计算,那么也会发生回流。

总结:

关于DOM元素

  • 添加或删除可见元素
  • 元素位置发生变化
  • 元素的尺寸大小发生变化
  • 页面初始化渲染

访问特定属性

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight

调用特定方法

  • getComputedStyle()
  • getBoundingClientRect()
  • currentStyle()

特定事件发生

  • 浏览器窗口尺寸改变,即resize事件
  • 页面初始化渲染

何时发生重绘

引起重绘的因素比较单一 :仅仅改变的是某元素的外观或者说风格,不影响整个页面的布局,不会影响DOM。

*** 如何减少回流 / 重绘

一、 避免逐次改变样式,样式统一批量修改

如果是一个属性一个属性改,那么就会引起多次回流:

const ele = document.getElementById('test');
ele.style.width = '100px';
ele.style.height = '200px';
ele.style.border = '1px solid black'

引起三次回流。

解决① 可以合并到一个类里,统一添加:

adding-style{
	width: 100px;
	height: 200px;
	border: 1px solid red;
}
const ele = document.getElementById('test');
ele.classList.add('adding-style');

解决② 使用cssText

const ele = document.getElementById('test');
ele.style.cssText += 'width: 100px; height: 200px; border: 1px solid red;';

二、 使DOM脱离文档流,再作批量处理

若要对DOM元素作多次处理修改,不作任何处理的话则会引起多次回流;

处理思路:

  • 使DOM元素脱离文档流
  • 对DOM元素作多次修改
  • 修改完,再把DOM元素带回文档流

具体实现:

解决① display:none、bababa处理、display:block

在进行一顿操作之前,先把DOM元素隐藏起来,操作完再让它显示出来。尽管隐藏和恢复显示会有2次回流,但至少隐藏之后不会因为一顿操作引起再多的回流。

const ele = document.getElementById('test');
ele.style.display = 'none';
// 隐藏成功,已脱离文档流,开始操作
// bababa
// bababa
// bababa
// 操作完成,可以加回文档流
ele.style.display = 'block';

解决② 使用document fragment在DOM Tree之外建立一个子树

const ele = document.getElementById('test');
const fragment = document.createDocumentFragment();
// 对子树 fragment操作
// bababa
// bababa
// bababa
// 添加回去
ele.appendChild(fragment);

解决③ 将待处理元素拷贝至一个脱离文档流的节点,处理完再代替原节点

const ele = document.getElementById('test');
const cloneNode = ele.cloneNode(true);
// 处理脱离文档流的节点cloneNode
// bababa
// bababa
// bababa
// 拿cloneNode代替原节点
ele.parentNode.replaceChild(cloneNode,ele);

三、 缓存布局属性(需即时计算的属性)

上面说过,当访问特定属性,浏览器为保证精确性、即时性,会再一次计算元素,从而引发回流。

故可以把这些属性缓存起来再使用。

若不缓存,访问一次则引发一次回流:

const ele = document.getElementById('test');
ele.style.left = ele.offsetLeft + 1 + 'px';  //回流
ele.style.top = ele.offsetTop + 1 + 'px';    //回流

缓存到变量:

const ele = document.getElementById('test');
var curLeft = ele.offsetLeft;  // 缓存到变量
var curTop = ele.offsetTop;    // 缓存到变量

ele.style.left = curLeft + 1 + 'px';  //不会回流
ele.style.top = curTop + 1 + 'px';    //不会回流

四、 position属性控制其脱离文档流

position可以设置absolute、fixed,分别对应的是绝对定位和固定定位。

这两种定位都会使得元素脱离文档流

那么把会多次回流的元素,将其设置为position:absolute或position:fixed

脱离文档流,则发生变化也不会再引起回流。

五、 CSS3硬件加速

能够触发CSS3硬件加速的属性,彻底不会引起回流:

  • transform
  • opacity
  • filters

以上是关于回流(重排)与重绘 —— 什么是回流和重绘,造成的原因是什么,如何去减少?的主要内容,如果未能解决你的问题,请参考以下文章

回流和重绘有啥区别?

JS的回流和重绘

带你认识什么是“回流重绘”

前端性能优化—回流与重绘

前端性能优化—回流与重绘

影响回流、重绘的 CSS 属性都有哪些?