想必大家写简单的页面早已经是得心应手了,但是如果写的页面要考虑性能消耗,那估计会难到很多初学者,在网页开发中,优化性能是非常重要的,如果写的网页一次性加载大量数据可能会出现一直卡住等现象。浏览器渲染过程中的回流(reflow)和重绘(repaint)是影响性能的关键环节。因此,本文将深入讲解重排和重绘的概念及其底层原理,并提供优化策略和代码示例。
浏览器渲染流程
在理解重排和重绘之前,我们需要了解浏览器渲染页面的流程。浏览器渲染页面通常包括以下几个步骤:
- 解析 HTML:将 HTML 转换为 DOM 树(Document Object Model)。
- 解析 CSS:将 CSS 转换为 CSSOM 树(CSS Object Model)。
- 生成渲染树:将 DOM 树和 CSSOM 树合并生成渲染树(Render Tree)。渲染树只包含可见元素,并且每个元素包含样式信息。
- 布局(Layout) :计算每个元素的位置和大小,生成布局树(Layout Tree)。
- 分层与图层合成:将布局树分层,生成多个图层,并合成最终的图层树。
- 绘制(Painting) :遍历图层树,绘制每个图层的内容到屏幕上。
布局与分层
布局阶段会计算每个元素的几何属性(位置和大小),生成布局树。布局树和渲染树类似,但包含几何信息。分层阶段将布局树中的元素分成多个图层,以便进行复杂的效果(如 3D 变换、z-index)。
图层合成与绘制
图层合成阶段将不同图层合成一棵图层树,然后在绘制阶段遍历图层树,将每个图层的内容绘制到屏幕上。
什么是回流(Reflow)?
回流,也称为重排(reflow),是指当网页布局信息发生改变时,浏览器重新计算元素的位置和大小的过程。例如,增加或减少元素、改变元素的尺寸、修改布局属性(如 margin、padding)等,都会触发重排。
回流示例
以下是一个简单的示例触发回流:
<!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: red;
}
</style>
</head>
<body>
<div id="box"></div>
<button onclick="triggerReflow()">Trigger Reflow</button>
<script>
function triggerReflow() {
var box = document.getElementById('box');
box.style.width = '200px'; // 修改宽度会触发重排
}
</script>
</body>
</html>
可以看到,当我们点击按钮将宽度修改了,就是改变了元素的布局,这样就是触发了回流,而且触发了回流就一定也会触发重绘。
什么是重绘(Repaint)?
重绘是指当元素的外观(例如颜色、背景图片)发生改变时,浏览器重新绘制元素的过程。重绘不涉及布局计算,因此相比重排开销较小。
重绘示例
以下是一个简单的示例触发重绘:
<!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: red;
}
</style>
</head>
<body>
<div id="box"></div>
<button onclick="triggerRepaint()">Trigger Repaint</button>
<script>
function triggerRepaint() {
var box = document.getElementById('box');
box.style.backgroundColor = 'blue'; // 修改背景颜色会触发重绘
}
</script>
</body>
</html>
当改变颜色后,就是触发了重绘效果,但是重绘只改变了元素的外观,并没有改变布局,所以触发了重绘不一定会回流。
回流与重绘的关系
这里总结一下两者的关系。重排一定会导致重绘,因为布局改变了,元素的外观也会随之改变。 重绘不一定会导致重排,因为有些情况下,元素的位置和大小并没有改变,只是外观发生了变化。
优化策略
避免频繁的样式修改
频繁修改样式会导致频繁的重排和重绘,从而影响性能。可以通过减少样式修改的次数来优化性能。例如,不要直接修改样式属性,而是通过切换类名来修改样式:
// 不推荐
element.style.width = '200px';
element.style.height = '200px';
// 推荐
element.classList.add('new-class');
使用 display: none 暂时移除 DOM 元素
在对元素进行多次操作之前,可以先将其从 DOM 树中移除,操作完成后再将其添加回去:
// 移除元素
element.style.display = 'none';
// 进行多次操作
element.style.width = '200px';
element.style.height = '200px';
// 添加回 DOM
element.style.display = 'block';
使用文档碎片减少 DOM 操作
通过使用文档碎片(DocumentFragment),可以将多次 DOM 操作合并为一次,从而减少重排和重绘的次数:
var fragment = document.createDocumentFragment();
for (var i = 0; i < 100; i++) {
var newElement = document.createElement('div');
newElement.textContent = 'Item ' + i;
fragment.appendChild(newElement);
}
document.body.appendChild(fragment);
优化 CSS 动画
尽量使用 CSS 动画而不是 JavaScript 来实现动画效果,因为浏览器对 CSS 动画进行了优化:
/* 使用 CSS 动画 */
@keyframes slidein {
from {
transform: translateX(0);
}
to {
transform: translateX(100px);
}
}
.element {
animation: slidein 1s forwards;
}
应用场景
当我们有大量数据要渲染,例如几万条及以上时,我们如果直接全放在一个页面去渲染,不仅会消耗很多性能,也会增加渲染时间。在这种情况下,我们可以采用如下策略:
1.使用文档碎片:
var fragment = document.createDocumentFragment();
for (var i = 0; i < 10000; i++) {
var newElement = document.createElement('div');
newElement.textContent = 'Item ' + i;
fragment.appendChild(newElement);
}
document.body.appendChild(fragment);
2.使用 requestAnimationFrame 替代 setTimeout:
var count = 0;
function renderItems() {
if (count < 10000) {
var newElement = document.createElement('div');
newElement.textContent = 'Item ' + count++;
document.body.appendChild(newElement);
requestAnimationFrame(renderItems);
}
}
requestAnimationFrame(renderItems);
3.减少布局触发:
避免频繁调用 offsetWidth、offsetHeight、getBoundingClientRect 等会触发重排的属性。因为重拍的性能消耗比较大。
4.使用懒加载: 对于大量图片或资源,使用懒加载技术:
<img src="image.jpg" loading="lazy" alt="Lazy Loaded Image">
或使用第三方库如 lozad.js:
<script src="https://cdn.jsdelivr.net/npm/lozad@1.14.0/dist/lozad.min.js"></script>
<script>
const observer = lozad();
observer.observe();
</script>
<img class="lozad" data-src="image.jpg" alt="Lazy Loaded Image">
通过应用这些优化策略,可以有效减少重排和重绘对性能的影响,从而提升网页的渲染速度和用户体验。
总结
通过深入理解浏览器渲染过程中的重排和重绘,我们可以采取相应的优化策略,减少不必要的渲染开销,提升网页性能。无论是通过减少样式修改、使用文档碎片、优化 CSS 动画,还是通过懒加载优化大数据渲染,这些方法都能帮助我们编写更高效的代码,提供更流畅的用户体验。希望本文的讲解和示例能够帮助大家更好地理解和优化网页性能。