v-for 渲染大量 Echarts 图表导致的白屏问题
优化 Vue 3 项目中大量 ECharts 柱状图的渲染效率
问题背景
在使用 Vue 3 开发过程中遇到一个问题:需要在一个页面上渲染 500 个 ECharts 柱状图。初步尝试直接通过 v-for
渲染这些组件时,出现了明显的白屏时间延长,这严重影响了用户体验。
效果
原效果:差不多有个 3s 的白屏时间
优化后:几乎是瞬间显示
问题分析
渲染大量组件导致白屏问题主要因为:
- 大量的 DOM 操作:每个图表都需要创建新的 DOM 元素,这导致浏览器需要处理大量的元素插入和渲染任务,耗费大量计算资源。
- JavaScript 执行阻塞:Vue 的响应式机制和组件的初始化需要大量的 JavaScript 计算,这在大规模渲染时会阻塞 UI 线程。
- 资源密集的图表计算:每个 ECharts 图表的渲染都需要进行数据处理和图形绘制,这一过程本身就非常消耗资源。
原代码
1、main.vue
<template>
<el-button type="primary" @click="isShow = !isShow">显示</el-button>
<el-button @click="setChartArrNumber(200)">200</el-button>
<el-button @click="setChartArrNumber(500)">500</el-button>
<el-button @click="setChartArrNumber(1000)">1000</el-button>
<testComponent v-if="isShow" :chartArr="chartArr" />
</template>
<script lang="ts" setup>
import { watch, ref } from "vue"
import testComponent from "./test/testComponent.vue"
const isShow = ref(false)
const chartArr = ref([])
const setChartArrNumber = (num) => {
chartArr.value = new Array(num).fill(0).map(() => {
return { data: [] }
})
}
</script>
2、testComponent.vue
<template>
<bar v-for="item in props.chartArr" :key="item" :chartData="item.data" />
</template>
<script lang="ts" setup>
import { watch, ref } from "vue"
import bar from "./bar.vue"
const props = defineProps({
chartArr: {
type: Object,
default: () => []
}
})
</script>
3、bar.vue
<template>
<div ref="chartRef" id="main"></div>
</template>
<script lang="ts" setup>
import * as echarts from "echarts"
import { onMounted, ref } from "vue"
const chartRef = ref(null)
const init = () => {
let myChart = echarts.init(chartRef.value)
let option = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
yAxis: {
type: "value"
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "bar"
}
]
}
option && myChart.setOption(option)
}
onMounted(() => {
init()
})
</script>
<style lang="scss" scoped>
#main {
width: 500px;
height: 400px;
}
</style>
解决方案:使用 requestAnimationFrame
为了优化性能和减少白屏时间,我使用了 requestAnimationFrame
(RAF) 进行图表采用分批渲染。RAF 允许我们将渲染任务安排在浏览器的下一个重绘之前执行,这有助于平滑地分配渲染负载,并避免在单一帧中做过多的工作,将图表的初始化和渲染分布到多个动画帧处理。这不仅使浏览器有机会在帧处理间进行必要的 UI 更新,还能保证用户界面的响应性和流畅性,从而显著减少白屏时间。。
在testComponent.vue 中加入 requestAnimationFrame
方法
<template>
<bar v-for="item in visibleComponents" :key="item" :chartData="item.data" />
</template>
<script lang="ts" setup>
import { watch, ref } from "vue"
import bar from "./bar.vue"
const props = defineProps({
chartArr: {
type: Object,
default: () => []
}
})
// 用于存储当前应该显示在页面上的组件的数据
const visibleComponents = ref([])
let animationFrameId = null // 用来存储requestAnimationFrame的ID
const loadComponents = () => {
// 取消之前的加载过程
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
}
visibleComponents.value = []
// 用于跟踪当前加载到的位置
let index = 0
// 表示每次加载的组件数量
const batchSize = 5
const loadBatch = () => {
// 切片取出下一批需要加载的数据
visibleComponents.value.push(...props.chartArr.slice(index, index + batchSize))
index += batchSize
if (index < props.chartArr.length) {
animationFrameId = requestAnimationFrame(loadBatch)
} else {
animationFrameId = null // 当所有数据加载完成,清除ID
}
}
loadBatch()
}
watch(
() => props.chartArr,
() => {
loadComponents()
}
)
</script>
<style lang="scss" scoped></style>
总结
使用 requestAnimationFrame
来优化 Vue 中使用 v-for 渲染大量的组件是一个有效的策略,特别是在处理大量数据可视化组件时。这种方法不仅改善了性能,还优化了用户的交互体验。
转载自:https://juejin.cn/post/7364547146286235689