在前端开发中,CSS性能对页面的加载速度和交互体验起着关键作用,其中重排(Reflow)和重绘(Repaint)机制是影响CSS性能的核心因素。深入理解并有效优化重排与重绘,能显著提升页面性能。在Vue使用环境下,重排和重绘有着独特的触发场景和优化策略。
6.1 重排与重绘的概念剖析
6.1.1 重排
重排,也叫回流,是当DOM的变化影响了元素的几何属性,如宽度、高度、位置、外边距、内边距、边框厚度等,浏览器需要重新计算元素的布局,并重新构建渲染树的过程。简单来说,就是页面布局发生了改变,浏览器要重新安排元素在页面上的位置和大小。例如,当你改变一个元素的 width 属性值,不仅该元素自身的宽度改变,其周围元素的布局也可能受到影响,浏览器需重新计算整个相关区域的布局,这就触发了重排。
6.1.2 重绘
重绘是当元素的外观发生变化,但不影响其布局时,浏览器重新绘制元素的过程。比如改变元素的 color 、 background - color 、 visibility 、 outline 等属性,这些变化不会改变元素在页面中的位置和大小,仅涉及元素的视觉呈现,所以只需要重新绘制该元素,而不需要重新计算布局,这就是重绘。
6.1.3 两者关系
重排比重绘的开销更大,因为重排通常会导致重绘。当发生重排时,元素的几何属性改变,其外观也可能随之改变,所以在重排之后往往会进行重绘;而重绘不一定会导致重排,比如仅改变元素颜色,只涉及重绘,不会触发重排。
6.2 常见触发重排与重绘的操作
6.2.1 触发重排的操作
- DOM结构改变:在Vue中,通过 v - if 、 v - for 指令动态添加、删除或移动DOM元素会触发重排。例如:
<template>
<div>
<button @click="toggleItem">切换元素</button>
<div v - if="showItem">这是一个动态元素</div>
</div>
</template>
<script>
export default {
data() {
return {
showItem: true
};
},
methods: {
toggleItem() {
this.showItem =!this.showItem;
}
}
};
</script>
点击按钮切换 showItem 的值,会添加或删除 div 元素,从而使DOM结构改变,触发重排。
- 元素几何属性改变:使用Vue的响应式数据绑定修改元素的 width 、 height 、 padding 、 margin 、 border 等属性会触发重排。如:
<template>
<div>
<button @click="increaseWidth">增加宽度</button>
<div :style="{ width: boxWidth + 'px' }"></div>
</div>
</template>
<script>
export default {
data() {
return {
boxWidth: 100
};
},
methods: {
increaseWidth() {
this.boxWidth += 10;
}
}
};
</script>
点击按钮增加 boxWidth 的值,改变了 div 的宽度,会导致其自身以及周围元素的布局重新计算,触发重排。
- 获取某些属性值:在Vue的方法或生命周期钩子中读取一些会触发布局计算的属性值时,也会触发重排,如 offsetWidth 、 offsetHeight 、 scrollTop 、 scrollLeft 、 clientTop 、 clientLeft 等。例如:
<template>
<div ref="myDiv">这是一个div</div>
</template>
<script>
export default {
mounted() {
console.log(this.$refs.myDiv.offsetWidth);
}
};
</script>
在 mounted 钩子中读取 offsetWidth 属性时,浏览器为了返回准确的值,需要先计算布局,这就触发了重排。
6.2.2 触发重绘的操作
- 改变元素外观属性:通过Vue绑定修改元素的 color 、 background - color 、 text - decoration 、 opacity 等会触发重绘。比如:
<template>
<div>
<button @click="changeColor">改变颜色</button>
<span :style="{ color: textColor }">这是一段文字</span>
</div>
</template>
<script>
export default {
data() {
return {
textColor: 'black'
};
},
methods: {
changeColor() {
this.textColor ='red';
}
}
};
</script>
点击按钮改变 textColor 的值,仅改变了文字的颜色,会触发重绘。
- 改变元素的 outline 属性:例如:
<template>
<div>
<button @click="addOutline">添加轮廓</button>
<button @click="removeOutline">移除轮廓</button>
<div :style="{ outline: outlineStyle }">这是一个div</div>
</div>
</template>
<script>
export default {
data() {
return {
outlineStyle: 'none'
};
},
methods: {
addOutline() {
this.outlineStyle = '1px solid blue';
},
removeOutline() {
this.outlineStyle = 'none';
}
}
};
</script>
点击按钮修改 outlineStyle 的值,会触发重绘。
6.3 避免频繁重排与重绘的优化策略
6.3.1 批量修改DOM样式
在Vue中频繁修改DOM元素的样式会导致多次重排重绘。例如,以下代码会触发多次重排:
<template>
<div ref="myDiv">这是一个div</div>
</template>
<script>
export default {
mounted() {
const div = this.$refs.myDiv;
div.style.width = '100px';
div.style.height = '100px';
div.style.backgroundColor ='red';
}
};
</script>
每一次样式修改都会触发一次重排和重绘,因为浏览器需要根据新的样式重新计算元素的布局和外观。正确的做法是将所有样式修改合并到一个类中,然后通过Vue的 class 绑定一次性添加或切换类名:
<template>
<div :class="['myDivClass', { active: isActive }]" ref="myDiv">这是一个div</div>
</template>
<style scoped>
.myDivClass {
width: 100px;
height: 100px;
background - color: red;
}
</style>
<script>
export default {
data() {
return {
isActive: false
};
},
mounted() {
this.isActive = true;
}
};
</script>
这样,浏览器只需要在添加类名时进行一次重排和重绘,大大提高了性能。
6.3.2 使用文档片段(DocumentFragment)
当需要在Vue中对DOM进行大量操作时,使用文档片段可以减少重排次数。例如,通过 v - for 指令动态渲染一个列表,假设要向列表中添加多个项目:
<template>
<div>
<button @click="addItems">添加项目</button>
<ul ref="itemList"></ul>
</div>
</template>
<script>
export default {
methods: {
addItems() {
const list = this.$refs.itemList;
const fragment = document.createDocumentFragment();
const items = ['商品1', '商品2', '商品3'];
items.forEach(item => {
const li = document.createElement('li');
li.textContent = item;
fragment.appendChild(li);
});
list.appendChild(fragment);
}
}
};
</script>
先将所有新元素添加到文档片段中,最后一次性将文档片段添加到列表中,这样只触发一次重排,而不是每次添加元素都触发重排。
6.3.3 避免读取会触发重排的属性
在Vue中尽量减少在修改样式期间读取会触发重排的属性。例如:
<template>
<div ref="myDiv">这是一个div</div>
</template>
<script>
export default {
methods: {
updateDiv() {
const div = this.$refs.myDiv;
// 错误做法,多次读取offsetWidth会导致多次重排
for (let i = 0; i < 10; i++) {
div.style.width = (div.offsetWidth + 10) + 'px';
}
// 正确做法,先缓存offsetWidth值
const width = div.offsetWidth;
for (let i = 0; i < 10; i++) {
div.style.width = (width + 10) + 'px';
}
}
}
};
</script>
在循环中多次读取 offsetWidth 会导致每次读取都触发重排,先缓存该值再进行计算和修改,可以避免不必要的重排。
6.3.4 使用CSS3硬件加速
在Vue项目中利用CSS3的 transform 和 opacity 属性,可开启硬件加速,将渲染任务交给GPU处理,提升动画和过渡效果的流畅度,同时减少重排重绘。例如,在实现元素的淡入淡出效果时,使用 opacity 属性结合Vue的过渡组件:
<template>
<div>
<button @click="toggleFade">切换淡入淡出</button>
<transition name="fade">
<div v - if="showDiv" class="fade - in" :style="{ opacity: fadeOpacity }">这是一个淡入淡出的div</div>
</transition>
</div>
</template>
<style scoped>
.fade - enter - active,
.fade - leave - active {
transition: opacity 0.3s ease - in - out;
}
.fade - enter,
.fade - leave - to {
opacity: 0;
}
</style>
<script>
export default {
data() {
return {
showDiv: true,
fadeOpacity: 1
};
},
methods: {
toggleFade() {
this.showDiv =!this.showDiv;
this.fadeOpacity = this.showDiv? 1 : 0;
}
}
};
</script>
在实现元素移动时,使用 transform 属性代替 left 、 top 等属性,结合Vue的动画指令:
<template>
<div>
<button @click="moveDiv">移动div</button>
<div :style="{ transform: 'translateX(' + moveDistance + 'px)' }" class="move - right">这是一个可移动的div</div>
</div>
</template>
<style scoped>
.move - right {
transition: transform 0.3s ease - in - out;
}
</style>
<script>
export default {
data() {
return {
moveDistance: 0
};
},
methods: {
moveDiv() {
this.moveDistance += 100;
}
}
};
</script>
因为 transform 和 opacity 的改变不会影响元素的布局,所以不会触发重排,仅可能触发重绘,且利用GPU加速,能提高渲染性能。