回流(重排)与重绘
浏览器的渲染原理
浏览器的渲染原理步骤:
- 解析文档(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