在前端开发中,网页性能优化是一个永恒的话题。其中,回流(Reflow)和重绘(Repaint)是影响网页性能的重要因素。理解它们的原理和触发机制,能够帮助我们编写出性能更优的代码。
布局的难点与 BFC/FFC
在网页布局中,列式布局和理解 BFC/FFC 是一大难点。BFC 即 Block Formatting Context,中文名为块级格式化上下文。它是页面中的一块渲染区域,有一套渲染规则来约束块级盒子的布局,并且与这个区域外部毫不相干。
BFC 的基本概念
HTML 根元素是最外层的第一个 BFC 元素。在 BFC 中,块级元素从上到下排列,行内元素从左到右排列,这就形成了我们所说的文档流。例如,以下简单的 HTML 代码:
<!DOCTYPE html>
<html>
<head>
<title>BFC 示例</title>
</head>
<body>
<div style="background-color: lightblue;">块级元素 1</div>
<div style="background-color: lightgreen;">块级元素 2</div>
<span>行内元素 1</span>
<span>行内元素 2</span>
</body>
</html>
在这个例子中,div
元素作为块级元素,会从上到下排列;而 span
元素作为行内元素,会从左到右排列。
触发 BFC 的方式
有多种方式可以触发 BFC,例如 float
属性不为 none
、overflow
属性不为 visible
、display
属性为 flex
等。以下是一个使用 overflow: hidden
触发 BFC 的示例:
<!DOCTYPE html>
<html>
<head>
<title>BFC 触发示例</title>
<style>
.parent {
overflow: hidden;
background-color: lightblue;
}
.child {
float: left;
width: 100px;
height: 100px;
background-color: lightgreen;
}
</style>
</head>
<body>
<div class="parent">
<div class="child"></div>
</div>
</body>
</html>
在这个例子中,父元素 parent
使用 overflow: hidden
触发了 BFC,从而包含了浮动的子元素 child
。
列式布局与 table
标签
在早期的网页布局中,table
标签常被用于列式布局。然而,如今这种方式已经不太推荐使用了,主要原因有以下几点:
- 触发太多的回流和重绘:
table
布局中局部的改变会影响整个table
的回流重排,性能开销较大。 - 语义不符:
table
标签主要用于展示数据表,用它来做布局不符合语义化的要求。 - 不够灵活:
table
布局的结构相对固定,不够灵活,难以适应不同的屏幕尺寸和布局需求。
回流与重绘的基本概念
回流(Reflow)
回流,也称为重排,是指当 RenderTree 中部分或全部元素的尺寸、结构,或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程。例如,当我们改变元素的宽度、高度、位置等属性时,就会触发回流。
<!DOCTYPE html>
<html>
<head>
<title>回流示例</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: lightblue;
}
</style>
</head>
<body>
<div id="box"></div>
<button onclick="changeWidth()">改变宽度</button>
<script>
function changeWidth() {
const box = document.getElementById('box');
box.style.width = '200px';
}
</script>
</body>
</html>
在这个例子中,点击按钮会改变 box
元素的宽度,从而触发回流。
重绘(Repaint)
重绘是指当页面元素样式的改变并不影响它在文档流中的位置时,浏览器重新绘制元素的过程。例如,改变元素的颜色、背景色、可见性等属性,只会触发重绘,不会触发回流。
<!DOCTYPE html>
<html>
<head>
<title>重绘示例</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: lightblue;
}
</style>
</head>
<body>
<div id="box"></div>
<button onclick="changeColor()">改变颜色</button>
<script>
function changeColor() {
const box = document.getElementById('box');
box.style.backgroundColor = 'lightgreen';
}
</script>
</body>
</html>
在这个例子中,点击按钮会改变 box
元素的背景色,从而触发重绘。
触发回流的方式
回流是一个比较昂贵的操作,因为它需要重新计算元素的布局和位置。以下是一些常见的触发回流的方式:
页面首次渲染
页面首次渲染是最耗时的回流操作,因为它需要从无到有地构建整个页面的布局。据统计,网页每满 0.1s 加载时间可能会少 1000 万用户,因此优化首次渲染性能非常重要。
浏览器窗口的大小改变
当用户调整浏览器窗口的大小时,页面中的元素布局可能会发生变化,从而触发回流。为了减少这种情况下的性能开销,可以使用 resize
事件的防抖函数。
function debounce(func, delay) {
let timer = null;
return function() {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(() => {
func.apply(context, args);
}, delay);
};
}
window.addEventListener('resize', debounce(() => {
// 处理窗口大小改变的逻辑
}, 200));
元素尺寸或位置发生改变
当元素的尺寸或位置发生改变时,如修改 width
、height
、margin
、padding
等属性,会触发回流。不过,transtion
、transform
、opacity
等属性在新图层中不会触发回流,因此可以利用这些属性来实现动画效果,提高性能。
元素内容改变
当元素的文本内容、图片大小、列表项增加或删除时,也会触发回流。例如,使用 appendChild()
或 removeChild()
方法操作 DOM 元素时,就会触发回流。
display
属性的改变
当元素的 display
属性从 none
变为 block
或从 block
变为 none
时,会触发回流。因为 display: none
的元素不会参与布局,而 display: block
的元素会参与布局。
字体大小的变化
修改元素的字体大小会影响元素的布局,从而触发回流。因此,在实际开发中,尽量避免频繁修改字体大小。
激活 CSS 伪类
当激活 CSS 伪类,如 :hover
时,浏览器需要重新计算元素的样式和布局,可能会触发回流。例如:
<!DOCTYPE html>
<html>
<head>
<title>伪类触发回流示例</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: lightblue;
}
#box:hover {
width: 200px;
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
查询某些属性或调用某些方法
当查询某些属性或调用某些方法时,如 img.getBoundingClientRect()
,浏览器为了返回最新的布局信息,会强制触发回流。因此,尽量避免在频繁的循环中查询这些属性或调用这些方法。
性能优化建议
了解了回流和重绘的原理和触发机制后,我们可以采取一些措施来优化网页性能:
减少回流和重绘的次数
尽量批量修改元素的样式,而不是一次修改一个样式。例如,使用 class
来修改元素的样式:
<!DOCTYPE html>
<html>
<head>
<title>批量修改样式示例</title>
<style>
.new-style {
width: 200px;
height: 200px;
background-color: lightgreen;
}
</style>
</head>
<body>
<div id="box"></div>
<button onclick="changeStyle()">修改样式</button>
<script>
function changeStyle() {
const box = document.getElementById('box');
box.classList.add('new-style');
}
</script>
</body>
</html>
使用 display: none
进行批量操作
如果需要对一个元素进行多次修改,可以先将其设置为 display: none
,进行批量修改后再将其显示出来。因为 display: none
的元素不会参与布局,所以不会触发回流和重绘。
使用 transform
实现动画效果
transform
属性在新图层中不会触发回流,因此可以利用它来实现动画效果,提高性能。例如:
<!DOCTYPE html>
<html>
<head>
<title>transform 动画示例</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: lightblue;
transition: transform 1s;
}
#box:hover {
transform: translateX(100px);
}
</style>
</head>
<body>
<div id="box"></div>
</body>
</html>
总结
回流和重绘是影响网页性能的重要因素,理解它们的原理和触发机制,能够帮助我们编写出性能更优的代码。在实际开发中,我们应该尽量减少回流和重绘的次数,合理使用 transform
等属性,优化网页的渲染性能。希望本文能够帮助你更好地理解回流和重绘,提升你的前端开发技能。