『JavaScript』DOM回流与重绘深度剖析:提升Web应用性能的必修课

发布时间:2023年12月28日

请添加图片描述

请添加图片描述
📣读完这篇文章里你能收获到

  • 理解DOM回流(Reflow)和重绘(Repaint)的基本概念及其区别
  • 了解哪些DOM操作会触发回流和重绘
  • 探讨如何优化DOM操作以减少不必要的回流和重绘
  • 学习使用现代浏览器提供的性能工具来检测和分析回流与重绘

请添加图片描述

请添加图片描述
在JavaScript中,对DOM的操作可能会引发浏览器的回流和重绘过程,这两个过程对页面性能有重要影响。
请添加图片描述

一、浏览器页面渲染流程

  1. 浏览器把获取到的HTML代码解析成1个DOM树。DOM树里包含了所有HTML标签,包括 display:none 隐藏,还有用JS动态添加的元素等。
  2. 浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式
  3. DOM Tree 和样式结构体组合后构建render tree, render tree类似于DOM tree,但区别很大,render tree能识别样式,render tree中每个NODE都有自己的style,而且 render tree不包含隐藏的节点 (比如display:none的节点,还有head节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。
  4. 一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。


请添加图片描述

二、回流与重绘的基本概念

1. 回流(Reflow)

当DOM元素的几何属性(如尺寸、位置)发生变化,或者网页的CSS样式改变时,浏览器需要重新计算元素的布局,这个过程称为回流。回流不仅影响发生改变的元素本身,还可能影响其后代元素以及同级元素的位置。回流对性能影响较大。
以下是一些常见的DOM操作,它们可能会触发回流:

  1. 改变元素的几何属性:当修改元素的width、height、padding、margin、top、left等与布局相关的属性时,会触发回流。
  2. 添加或删除可见的DOM元素:当向页面中添加或删除可见的元素时,会影响文档布局,从而触发回流。
  3. 改变元素的定位方式:当元素的position属性从static变为absolute或fixed时,可能会影响其他元素的位置,从而触发回流。
  4. 修改影响元素布局的CSS样式:改变一些影响布局的CSS样式,如display、float、flex、grid等,会触发回流。
  5. 激活CSS伪类:某些CSS伪类的激活,如:hover、:focus等,可能导致元素布局的变化,从而触发回流。
  6. 获取布局信息:使用offsetWidth、offsetHeight、getComputedStyle()等方法获取元素的布局信息时,如果浏览器需要重新计算布局以提供准确的数据,也会触发回流。
// 改变元素的几何属性(触发回流)
let element = document.getElementById("myElement");
element.style.width = "200px";

// 添加可见的DOM元素(触发回流)
let newElement = document.createElement("div");
document.body.appendChild(newElement);

// 改变元素的定位方式(触发回流)
let element = document.getElementById("myElement");
element.style.position = "absolute";

// 修改影响元素布局的CSS样式(触发回流)
let element = document.getElementById("myElement");
element.style.display = "flex";

// 激活CSS伪类(触发回流)
let element = document.getElementById("myElement");
element.classList.add("hovered");

// 获取布局信息(可能触发回流)
let element = document.getElementById("myElement");
let computedStyle = window.getComputedStyle(element);

2. 重绘(Repaint)

当元素的外观属性(如颜色、背景色、边框样式)发生变化,但不影响其几何属性时,浏览器只需要重新绘制受影响的部分,这个过程称为重绘。
以下是一些常见的DOM操作,它们可能会触发重绘:

  1. 改变元素的颜色、背景色、边框样式等外观属性:这些更改不会影响元素的位置或大小,但需要更新屏幕上的像素。
  2. 改变元素的透明度:透明度的变化会影响元素的视觉表现,但不会改变其布局。
  3. 修改文本内容:当元素的文本内容发生改变时,虽然不影响布局,但需要重新绘制文本。
  4. 激活CSS伪类:某些CSS伪类的激活,如:hover、:focus等,可能导致元素外观的变化,从而触发重绘。
// 改变元素的颜色、背景色、边框样式等外观属性(触发重绘)
let element = document.getElementById("myElement");
element.style.backgroundColor = "red";

// 改变元素的透明度(触发重绘)
let element = document.getElementById("myElement");
element.style.opacity = 0.5;

// 修改文本内容(触发重绘)
let element = document.getElementById("myElement");
element.textContent = "New text";

// 激活CSS伪类(触发重绘)
let element = document.getElementById("myElement");
element.classList.add("hovered");

// 在循环中修改DOM(多次触发回流和重绘)
for (let i = 0; i < 100; i++) {
  let childElement = document.createElement("div");
  childElement.textContent = i;
  document.body.appendChild(childElement);
}

请添加图片描述

三、DOM操作优化

以下是一些优化DOM操作以减少回流和重绘的策略,结合示例代码进行说明:

1. 基于文档碎片减少回流

如果需要在循环中修改多个元素,考虑先创建一个脱离文档的节点树(如documentFragment),在循环中修改这个临时节点树,最后将其插入到文档中。

//=> 创建文档碎片
let frg = document.createDocumentFragment();

data.forEach((item)=> {
  let List = document.createElement('li');
  //=> 每次把元素放入文档碎片中
  frg.appendChild(li)
});

//=> 最后再统一放入父元素中
parent.appendChild(frg);

2. 基于字符串拼接减少回流

把原有容器中的结构都以字符串的方式获取到,然后拼接成新的字符串,最后统一在插入到原有容器中。

let str = ``;
data.forEach(function (item) {
  str += `<li data-time="${time}" data-hot="${hot}" data-price="${price}">
    <a href="#">
      <img src="${picImg}" alt="">
      <p title="${title}">${title}</p>
      <span>¥${price}</span>
      <span>${hot}</span>
      <span>${time}</span>
    </a>
  </li>`;
});
parent.innerHTML = str;

理论上说,这种方式性能不如前面提到的文档碎片。因为这里还要把字符串变成 HTML 元素。但是这种方式很简单,所以真实项目中较常用。

3. 读写分离

把对 DOM 的读和写分开放到一起。减少在写操作的过程中又进行读操作,是基于前面的浏览器自身优化策略实现的。一次性把样式都操作完成。

// 引发两次回流
box.style.top = '100px';
box.style.top;
box.style.left = '100px';

// 引发一次回流
box.style.top = '100px';
box.style.left = '100px';
box.style.top;

4. 累积修改并一次性应用

将多个修改操作累积起来,然后一次性应用,可以避免多次触发回流和重绘。

// 避免不必要的DOM操作:累积修改并一次性应用
let elementsToUpdate = document.querySelectorAll(".myClass");
let styleChanges = [];

elementsToUpdate.forEach((element) => {
  styleChanges.push({
    element: element,
    width: "200px",
  });
});

styleChanges.forEach(({ element, width }) => {
  element.style.width = width;
});

5. 避免不必要的计算和查询

在需要频繁访问的属性或方法上缓存结果,避免重复计算和查询。

let myElement = document.getElementById("myElement");
let myStyle = window.getComputedStyle(myElement);

function updateStyles() {
  // 使用缓存的myStyle对象,避免重复查询
  let currentWidth = myStyle.getPropertyValue("width");
  // ...
}

请添加图片描述

四、性能工具的使用

现代浏览器提供了许多性能工具,可以帮助我们检测和分析回流与重绘:

  • Chrome DevTools:在"Performance"面板中记录页面加载和用户交互过程,查看回流和重绘事件的发生情况。
  • Firefox DevTools:在"Performance"面板中选择"Paint Flashing"选项,可以看到页面上发生重绘的区域。
  • Edge DevTools:在"Performance"面板中选择"Highlight updates when page is painted"选项,可以看到页面上发生重绘的区域。

请添加图片描述

请添加图片描述

文章来源:https://blog.csdn.net/qq_34202873/article/details/135233645
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。