第六章:CSS性能优化技巧之重排与重绘

发布于:2025-02-20 ⋅ 阅读:(121) ⋅ 点赞:(0)

在前端开发中,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加速,能提高渲染性能。


网站公告

今日签到

点亮在社区的每一天
去签到