在Web开发中,浏览器渲染网页的过程涉及多个步骤,其中“回流”(Reflow)和“重绘”(Repaint)是两个重要的概念。了解这两个过程有助于优化网页性能,减少不必要的渲染开销。
1 回流定义(Reflow)
定义:回流是指浏览器为了重新计算元素的几何属性(如位置和大小)而进行的操作。当DOM树中的元素发生改变时,浏览器需要重新计算这些元素的位置和大小,以确保页面布局的正确性。
触发回流的常见情况:
- 添加或删除可见的DOM元素。
- 元素的尺寸发生变化(如宽度、高度、字体大小等)。
- 元素的内容发生变化(如文本内容增加或减少)。
- CSS类的变化。
- 计算样式(如调用
offsetWidth和offsetHeight)。 - 页面滚动。
回流的影响:回流是一个相对昂贵的操作,因为它涉及到重新计算布局,可能会导致整个页面或部分页面的重新渲染。频繁的回流会严重影响页面性能。
2 重绘定义(Repaint)
定义:重绘是指浏览器为了更新元素的外观(如颜色、背景等)而进行的操作。当元素的样式发生变化但不影响其几何属性时,浏览器只需要重新绘制该元素即可。
触发重绘的常见情况:
- 改变元素的颜色或背景色。
- 改变元素的可见性(如
visibility属性)。 - 改变元素的文本内容。
- 改变元素的阴影、边框等不涉及几何属性的样式。
重绘的影响:重绘通常比回流便宜,因为它不需要重新计算布局。然而,频繁的重绘仍然会影响性能,尤其是在复杂页面中。
3 优化建议
-
批量操作DOM:尽量减少对DOM的频繁操作,可以将多个操作合并成一次操作。例如,使用
documentFragment来批量插入多个元素。 -
避免强制同步布局:避免在循环中多次读取和修改布局相关的属性,这会导致浏览器进行多次回流。可以先读取所有需要的属性,然后再进行修改。
-
使用CSS动画:使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。
-
使用
requestAnimationFrame:在JavaScript中进行布局相关的操作时,使用requestAnimationFrame可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。 -
避免使用表格布局:表格布局的回流成本较高,因为一个单元格的变化可能会影响整个表格的布局。尽量使用更灵活的布局方式,如Flexbox或Grid。
示例
以下是一个简单的示例,展示了如何通过批量操作DOM来减少回流和重绘:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Optimize Reflow and Repaint</title>
<style>
.container {
display: flex;
flex-direction: column;
}
.item {
height: 50px;
margin: 10px;
background-color: #4CAF50;
}
</style>
</head>
<body>
<div class="container" id="container"></div>
<script>
function addItems() {
const container = document.getElementById('container');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const item = document.createElement('div');
item.className = 'item';
fragment.appendChild(item);
}
container.appendChild(fragment);
}
addItems();
</script>
</body>
</html>
在这个示例中,我们使用 documentFragment 来批量插入多个元素,从而减少对DOM的频繁操作,提高性能。
更好地理解回流和重绘的概念,并在实际开发中进行优化。
4 引起回流的属性和方法
在Web开发中,某些CSS属性和JavaScript方法会触发浏览器的回流(Reflow)。了解这些属性和方法有助于优化页面性能,减少不必要的回流次数。以下是常见的引起回流的属性和方法:
4.1 引起回流的CSS属性
-
布局相关的属性:
width和heightmargin和paddingborder-widthtop,right,bottom,leftmin-width,max-width,min-height,max-heightdisplaypositionfloatbox-sizingtransform(某些情况下,特别是涉及translate以外的变换)
-
内容相关的属性:
font-sizeline-heighttext-indentword-spacingletter-spacing
-
其他影响布局的属性:
overflowz-indexvisibility(从hidden到visible或反之)
4.2 引起回流的JavaScript方法
-
获取布局信息的方法:
offsetTop,offsetLeft,offsetWidth,offsetHeightclientTop,clientLeft,clientWidth,clientHeightscrollTop,scrollLeft,scrollWidth,scrollHeightgetBoundingClientRect()getComputedStyle()
-
修改DOM结构的方法:
appendChild()insertBefore()removeChild()replaceChild()
-
修改样式的方法:
element.style.property = valueclassList.add(),classList.remove(),classList.toggle()setAttribute('style', '...')
-
其他引起回流的方法:
resize()(窗口大小改变)scroll()(滚动事件)focus()(焦点事件)
4.3 为什么这些方法会引起回流?
当调用这些方法时,浏览器需要确保返回的值是最新的,因此它会检查是否有任何布局变化需要重新计算。如果确实有变化,浏览器会进行回流以确保返回的值是准确的。
以下是一个简单的示例,展示了如何在循环中多次调用这些方法会引发多次回流:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reflow Example</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: #4CAF50;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
function measureBox() {
const box = document.getElementById('box');
for (let i = 0; i < 100; i++) {
const width = box.offsetWidth; // 每次调用都会触发回流
console.log(`Width: ${width}px`);
}
}
measureBox();
</script>
</body>
</html>
在这个示例中,每次调用 box.offsetWidth 都会触发一次回流,因为浏览器需要确保返回的宽度值是最新的。
4.4 优化建议
-
避免批量操作DOM:
- 尽量减少对DOM的频繁操作,可以将多个操作合并成一次操作。例如,使用
documentFragment来批量插入多个元素。
- 尽量减少对DOM的频繁操作,可以将多个操作合并成一次操作。例如,使用
-
避免强制同步布局:
- 避免在循环中多次读取和修改布局相关的属性,这会导致浏览器进行多次回流。可以先读取所有需要的属性,然后再进行修改。
-
使用CSS动画:
- 使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。
-
使用
requestAnimationFrame:- 在JavaScript中进行布局相关的操作时,使用
requestAnimationFrame可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。 - 在JavaScript中进行布局相关的操作时,使用
requestAnimationFrame可以确保操作在下一个重绘周期之前完成,从而减少不必要的回流和重绘。
function measureBox() { const box = document.getElementById('box'); requestAnimationFrame(() => { const width = box.offsetWidth; // 一次性读取 const height = box.offsetHeight; // 一次性读取 for (let i = 0; i < 100; i++) { console.log(`Width: ${width}px, Height: ${height}px`); } }); } measureBox(); - 在JavaScript中进行布局相关的操作时,使用
-
避免使用表格布局:
- 表格布局的回流成本较高,因为一个单元格的变化可能会影响整个表格的布局。尽量使用更灵活的布局方式,如Flexbox或Grid。
-
避免在循环中读取布局信息:
- 尽量避免在循环中多次读取布局信息,这会导致多次回流。可以将读取操作移到循环外部。
- 如果需要多次读取布局信息,可以先将所有需要的值一次性读取出来,然后再进行后续操作。
function measureBox() { const box = document.getElementById('box'); const width = box.offsetWidth; // 一次性读取 const height = box.offsetHeight; // 一次性读取 for (let i = 0; i < 100; i++) { console.log(`Width: ${width}px, Height: ${height}px`); } } measureBox();
4.5 总结
获取布局信息的方法确实会触发回流,因为浏览器需要确保返回的值是最新的。通过优化读取布局信息的方式,可以显著减少不必要的回流,提高页面性能。
5 引起重绘的属性和方法
在Web开发中,某些CSS属性和JavaScript方法会触发浏览器的重绘(Repaint)。了解这些属性和方法有助于优化页面性能,减少不必要的重绘次数。以下是常见的引起重绘的属性和方法:
5.1 引起重绘的CSS属性
-
颜色和背景相关的属性:
colorbackground-colorbackground-imageborder-coloroutline-colortext-decoration-colortext-shadow
-
文本和字体相关的属性:
font-familyfont-weightfont-styletext-aligntext-transformwhite-spaceword-breakword-wrap
-
透明度和滤镜相关的属性:
opacityfilter
-
可见性相关的属性:
visibilitydisplay(从none到其他值或反之)
-
其他影响外观的属性:
box-shadowborder-radiusclipcursorlist-style-image
5.2 引起重绘的JavaScript方法
-
颜色和背景相关的属性:
window.getComputedStyle(element).colorwindow.getComputedStyle(element).backgroundColorwindow.getComputedStyle(element).borderColorwindow.getComputedStyle(element).textDecorationColorwindow.getComputedStyle(element).textShadow
-
文本和字体相关的属性:
window.getComputedStyle(element).fontFamilywindow.getComputedStyle(element).fontWeightwindow.getComputedStyle(element).fontStylewindow.getComputedStyle(element).textAlignwindow.getComputedStyle(element).textTransformwindow.getComputedStyle(element).whiteSpacewindow.getComputedStyle(element).wordBreakwindow.getComputedStyle(element).wordWrap
-
透明度和滤镜相关的属性:
window.getComputedStyle(element).opacitywindow.getComputedStyle(element).filter
-
可见性相关的属性:
window.getComputedStyle(element).visibilitywindow.getComputedStyle(element).display
-
其他影响外观的属性:
window.getComputedStyle(element).boxShadowwindow.getComputedStyle(element).borderRadiuswindow.getComputedStyle(element).clipwindow.getComputedStyle(element).cursorwindow.getComputedStyle(element).listStyleImage
-
修改样式的方法:
element.style.property = valueclassList.add(),classList.remove(),classList.toggle()setAttribute('style', '...')
-
修改DOM内容的方法:
innerHTMLtextContentinnerText
-
其他引起重绘的方法:
scrollIntoView()focus()blur()
以下是一个简单的示例,展示了如何通过批量修改样式来减少重绘次数:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Optimize Repaint</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: #4CAF50;
color: white;
text-align: center;
line-height: 100px;
}
.new-class {
background-color: red;
color: black;
}
</style>
</head>
<body>
<div class="box" id="box">Hello</div>
<script>
function changeStyle() {
const box = document.getElementById('box');
box.classList.add('new-class'); // 一次性修改多个样式
}
changeStyle();
</script>
</body>
</html>
在这个示例中,我们通过添加一个CSS类来一次性修改多个样式,从而减少重绘次数。
5.3 为什么这些方法可能会引起重绘?
虽然获取这些属性和方法本身不会直接引起重绘,但在某些情况下,浏览器为了确保返回的值是最新的,可能会进行重绘。例如,如果你在一个动画循环中频繁获取这些属性,浏览器可能会在每次获取时进行重绘以确保返回的值是最新的。
以下是一个简单的示例,展示了如何在循环中多次获取样式属性可能会导致重绘:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Repaint Example</title>
<style>
.box {
width: 100px;
height: 100px;
background-color: #4CAF50;
color: white;
text-align: center;
line-height: 100px;
}
</style>
</head>
<body>
<div class="box" id="box">Hello</div>
<script>
function measureBox() {
const box = document.getElementById('box');
for (let i = 0; i < 100; i++) {
const color = window.getComputedStyle(box).color; // 每次调用可能会引起重绘
console.log(`Color: ${color}`);
}
}
measureBox();
</script>
</body>
</html>
在这个示例中,每次调用 window.getComputedStyle(box).color 都可能会引起一次重绘,因为浏览器需要确保返回的颜色值是最新的。
5.4 优化建议
-
批量读取样式信息:
- 如果需要多次读取样式信息,可以先将所有需要的值一次性读取出来,然后再进行后续操作。
function measureBox() { const box = document.getElementById('box'); const style = window.getComputedStyle(box); // 一次性读取 const color = style.color; const backgroundColor = style.backgroundColor; for (let i = 0; i < 100; i++) { console.log(`Color: ${color}, Background Color: ${backgroundColor}`); } } measureBox(); -
批量修改样式:
- 尽量减少对样式的频繁修改,可以将多个样式修改合并成一次操作。例如,使用CSS类来批量修改样式。
function changeStyle() { const element = document.getElementById('myElement'); element.classList.add('new-class'); // 一次性添加多个样式 } function changeText() { const elements = document.querySelectorAll('.text-element'); const newContent = 'New Content'; elements.forEach(element => { element.textContent = newContent; // 一次性修改 }); } -
使用
requestAnimationFrame:- 在JavaScript中进行样式相关的操作时,使用
requestAnimationFrame可以确保操作在下一个重绘周期之前完成,从而减少不必要的重绘。
function measureBox() { const box = document.getElementById('box'); requestAnimationFrame(() => { const style = window.getComputedStyle(box); // 一次性读取 const color = style.color; const backgroundColor = style.backgroundColor; for (let i = 0; i < 100; i++) { console.log(`Color: ${color}, Background Color: ${backgroundColor}`); } }); } measureBox(); - 在JavaScript中进行样式相关的操作时,使用
-
使用CSS动画:
- 使用CSS动画而不是JavaScript来实现动画效果,因为CSS动画可以在GPU上进行,性能更好。
.animate { transition: background-color 0.5s ease; }function startAnimation() { const element = document.getElementById('myElement'); element.classList.add('animate'); element.style.backgroundColor = 'red'; } -
使用
requestAnimationFrame:- 在JavaScript中进行样式相关的操作时,使用
requestAnimationFrame可以确保操作在下一个重绘周期之前完成,从而减少不必要的重绘。
function changeStyle() { const element = document.getElementById('myElement'); requestAnimationFrame(() => { element.style.color = 'blue'; element.style.backgroundColor = 'yellow'; }); } - 在JavaScript中进行样式相关的操作时,使用
5.5 总结
获取某些CSS属性和JavaScript方法本身不会直接引起重绘,但在某些情况下,浏览器为了确保返回的值是最新的,可能会进行重绘。通过优化读取样式信息的方式,可以显著减少不必要的重绘,提高页面性能。
PS:学会了记得,点赞,评论,收藏,分享