重绘和回流

一、什么是重绘与回流

重绘(Repaint)

重绘是指当元素的外观发生变化时,浏览器需要重新绘制这些元素。由于这些操作不会改变元素占据的空间,因此不需要进行回流。常见的重绘操作包括:

改变颜色、背景色、透明度

 

回流(Reflow)
回流(也称为布局重排)是指当元素的尺寸、位置或其他影响其布局的属性发生变化时,浏览器需要重新计算元素的布局,并重新构建渲染树(Render Tree)的过程。在回流发生时,涉及到的元素及其后代会被重新计算并绘制,这通常会导致性能下降。

 

常见的回流操作包括:

更改元素的尺寸(如设置 width、height、padding、border、margin)。
添加或删除可见的DOM元素(包括添加或删除元素)。
修改元素的内容(如文本变化、数量,图片数量、大小、字体大小)。
改变视口(如窗口缩放、滚动等)。
页面初始渲染。
浏览器窗口大小改变。
激活CSS伪类(如 :hover)。
查询某些属性或调用某些方法:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTopoffsetLeft
  • scrollWidthscrollHeightscrollTopscrollLeft
  • scrollIntoView()scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()
 

性能影响:

回流 > 重绘。值得注意的是,回流一定会引起重绘,但重绘不一定会导致回流。

如何优化?

  • 样式集中处理:将样式的修改集中到一个步骤中,而不是逐个修改。这可以通过创建一个样式数组,通过更改一个元素的 class 来实现样式的切换,从而减少操作次数。
// 1. 不集中处理样式(直接修改样式属性)
document.getElementById("update").addEventListener("click", () => {
    const boxes = document.querySelectorAll(".box");
    boxes.forEach((box) => {
        box.style.width = "200px"; // 直接修改样式属性
        box.style.height = "200px";
        box.style.backgroundColor = "red";
        box.style.transform = "translateX(50px)";
    });
});

// 1. 减少回流次数:通过集中处理样式
document.getElementById("update").addEventListener("click", () => {
    const boxes = document.querySelectorAll(".box");
    boxes.forEach((box) => {
        box.classList.add("new-style"); // 通过添加类名来集中更新样式
    });
});
  • 通过变量存储调用 offsetWidth 结果:当需要多次获取元素的布局信息(如 offsetWidth),应将其存储在变量中以避免重复计算,从而减少回流。
// 2. 不使用变量存储 `offsetWidth` 结果(多次调用 `offsetWidth`)
document.getElementById("addBox").addEventListener("click", () => {
    const container = document.getElementById("container");
    console.log("Container Width:", container.offsetWidth); // 多次调用 offsetWidth
    console.log("Container Width:", container.offsetWidth); // 多次调用 offsetWidth
    console.log("Container Width:", container.offsetWidth); // 多次调用 offsetWidth
    const newBox = document.createElement("div");
    newBox.className = "box new-style";
    container.appendChild(newBox);
});

// 2. 使用变量存储调用 offsetWidth 结果:避免重复计算  
const container = document.getElementById('container');  
const containerWidth = container.offsetWidth;  // 只计算一次  
console.log('Container Width:', containerWidth); // 输出容器宽度 
  • 在进行多次DOM更新时,可以使用文档片段(DocumentFragment)来批量更新DOM,避免频繁的回流。
// 3. DOM 离线操作:使用文档片段添加多个元素  
document.getElementById('addBox').addEventListener('click', () => {  
    const fragment = document.createDocumentFragment();  
    const newBox = document.createElement('div');  
    newBox.className = 'box new-style'; // 直接添加样式类  
    fragment.appendChild(newBox);  
    // 一次性添加,避免多次回流  
    container.appendChild(fragment);  
});

// 3. 不使用文档片段(直接添加元素到 DOM)
document.getElementById("addBox").addEventListener("click", () => {
    const container = document.getElementById("container");
    const newBox = document.createElement("div");
    newBox.className = "box new-style";
    container.appendChild(newBox); // 直接添加元素到 DOM
    const newBox2 = document.createElement("div");
    newBox2.className = "box new-style";
    container.appendChild(newBox2); // 直接添加元素到 DOM
    const newBox3 = document.createElement("div");
    newBox3.className = "box new-style";
    container.appendChild(newBox3); // 直接添加元素到 DOM
});
  • 对于一些大型操作,使用绝对定位或固定定位可以将元素脱离文档流,从而减少回流的影响。
  • 避免使用CSS表达式(例如:calc()
  • 避免使用table布局:由于浏览器使用流式布局,对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间

 

posted @ 2025-02-11 10:04  我是格鲁特  阅读(283)  评论(0)    收藏  举报