『JavaScript』DOM回流与重绘深度剖析:提升Web应用性能的必修课
2023-12-28 16:54:18
📣读完这篇文章里你能收获到
- 理解DOM回流(Reflow)和重绘(Repaint)的基本概念及其区别
- 了解哪些DOM操作会触发回流和重绘
- 探讨如何优化DOM操作以减少不必要的回流和重绘
- 学习使用现代浏览器提供的性能工具来检测和分析回流与重绘
文章目录
在JavaScript中,对DOM的操作可能会引发浏览器的回流和重绘过程,这两个过程对页面性能有重要影响。
一、浏览器页面渲染流程
- 浏览器把获取到的HTML代码解析成1个DOM树。DOM树里包含了所有HTML标签,包括 display:none 隐藏,还有用JS动态添加的元素等。
- 浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式
- 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),理解页面元素为一个具有填充、边距、边框和位置的盒子。
- 一旦render tree构建完毕后,浏览器就可以根据render tree来绘制页面了。
二、回流与重绘的基本概念
1. 回流(Reflow)
当DOM元素的几何属性(如尺寸、位置)发生变化,或者网页的CSS样式改变时,浏览器需要重新计算元素的布局,这个过程称为回流。回流不仅影响发生改变的元素本身,还可能影响其后代元素以及同级元素的位置。回流对性能影响较大。
以下是一些常见的DOM操作,它们可能会触发回流:
- 改变元素的几何属性:当修改元素的width、height、padding、margin、top、left等与布局相关的属性时,会触发回流。
- 添加或删除可见的DOM元素:当向页面中添加或删除可见的元素时,会影响文档布局,从而触发回流。
- 改变元素的定位方式:当元素的position属性从static变为absolute或fixed时,可能会影响其他元素的位置,从而触发回流。
- 修改影响元素布局的CSS样式:改变一些影响布局的CSS样式,如display、float、flex、grid等,会触发回流。
- 激活CSS伪类:某些CSS伪类的激活,如:hover、:focus等,可能导致元素布局的变化,从而触发回流。
- 获取布局信息:使用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操作,它们可能会触发重绘:
- 改变元素的颜色、背景色、边框样式等外观属性:这些更改不会影响元素的位置或大小,但需要更新屏幕上的像素。
- 改变元素的透明度:透明度的变化会影响元素的视觉表现,但不会改变其布局。
- 修改文本内容:当元素的文本内容发生改变时,虽然不影响布局,但需要重新绘制文本。
- 激活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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!