重排(回流)和重绘

109 阅读4分钟

回流(Reflow)

浏览器为了重新渲染部分或全部文档而重新计算元素的几何属性的过程。这包括了元素的尺寸、位置、布局和页面的结构。回流是一种比较昂贵的操作,因为它会触发浏览器重新计算并重新绘制元素,可能导致性能下降。

重绘(Repaint)

当元素样式发生改变,但没有影响其布局时,浏览器只需要重新绘制元素的外观的过程。这不涉及元素的位置或尺寸的改变。相比于回流,重绘是一种较为轻量级的操作。

触发回流

  • 页面初始渲染,这是开销最大的一次重排;
  • 改变窗口大小。
  • 改变元素字体大小;
  • 改变元素的尺寸、位置、边距、填充等。
  • 修改元素的内容,例如文本或图片。
  • 添加或删除可见的DOM元素。
  • 激活CSS伪类,例如 :hover。
  • 查询某些属性或调用某些方法,需要布局信息,如 offsetWidth、offsetHeight、clientWidth、clientHeight、getComputedStyle、scrollWidth、scrollHeight 等。
  • 设置 style 属性的值

触发重绘

  • 修改元素的背景色、文字颜色、边框颜色等。
  • 改变元素的可见性,例如使用 visibility: hidden。
  • 使用CSS3的某些属性,如 transform 和 opacity,但不影响布局。

影响

回流和重绘操作会对性能产生直接的影响,因为它们需要浏览器重新计算和绘制页面的一部分或全部内容。这可能会导致页面的性能下降,用户可能会感到页面加载缓慢或卡顿。

1. 性能下降

触发回流和重绘的操作通常比较昂贵,因为它们需要浏览器重新计算和渲染元素的布局和外观。如果这些操作频繁发生,页面的性能将明显下降,用户可能会感到页面加载缓慢或卡顿。

2. 电池寿命问题

频繁的回流和重绘操作会增加设备的能耗,尤其是在移动设备上。这可能导致设备电池寿命的缩短,影响用户的体验。

3. 用户交互延迟

回流和重绘操作也可能导致用户交互的延迟。当浏览器正在执行这些操作时,用户可能无法立即响应页面上的交互,这会影响用户体验。

如何优化回流和重绘

使用transform和opacity

避免使用display:block

CSS批量操作

不推荐的写法

element.style.width = '100px';
element.style.height = '100px';

推荐的写法

<style>
  .block{
    width: 100px;
    height: 100px;
  }
</style>
<script>
element.className = 'block';
</script>

DOM批量操作

const ulEle = document.getElementById("father");
let arr = [];
arr = "我是0号,我后面还有1号,2号,3号,4号,5号", "我是2号", "我是3号", "我是4号", "我是5号"]; // 我是动态获取的
arr.forEach(element => {
  const childNode = document.createElement('li');
  childNode.innerText = element;
  ulEle.appendChild(childNode);// 每一次都会引起重排(重排会引起重绘)
})
const ulEle = document.getElementById("demo");
const childUlNode = document.createElement('ul');
let arr = [];
arr = ["我是0号,我后面还有1号,2号,3号,4号,5号","我是1号", "我是2号", "我是3号", "我是4号", "我是5号"]; // 我是动态获取的
arr.forEach(element => {
  const childLiNode = document.createElement('li');
  childLiNode.innerText = element;
  childUlNode.appendChild(childLiNode);
})
ulEle.appendChild(childUlNode);// 只会引起一次重排(重排会引起重绘)

requestAnimationFrame

requestAnimationFrame是一个用于执行动画的API,它可以帮助你在浏览器下一次重新渲染之前执行动画操作。这可以减少回流和重绘的发生,因为浏览器会将多个操作合并到一次渲染中。

function animate() {
  // 执行动画操作
  // ...
}
// 启动动画
requestAnimationFrame(animate);

缓存

尽量避免频繁读取布局属性,因为这会触发回流。如果需要多次使用某个属性的值,最好将其缓存起来,而不是多次查询。

// 不推荐
const offsetWidth = '100px';
const renderEle = document.getElementById('demo');
renderEle.style.offsetWidth = offsetWidth // 导致重绘(写入)
const tempoOffsetWidth = renderEle.style.offsetWidth // 读取可能会导致重排

// 推荐的写法:缓存属性值
const offsetWidth = '100px';
const renderEle = document.getElementById('demo');
renderEle.style.offsetWidth = offsetWidth // 导致重绘(写入)
const tempoOffsetWidth = renderEle; // 避免直接读取offsetWidth

使用CSS硬件加速

  • transform
  • opacity
  • filter
  • will-change

防抖节流

参考