v-for 渲染大量 Echarts 图表导致的白屏问题

发布于:2024-05-06 ⋅ 阅读:(33) ⋅ 点赞:(0)

优化 Vue 3 项目中大量 ECharts 柱状图的渲染效率

问题背景

在使用 Vue 3 开发过程中遇到一个问题:需要在一个页面上渲染 500 个 ECharts 柱状图。初步尝试直接通过 v-for 渲染这些组件时,出现了明显的白屏时间延长,这严重影响了用户体验。

效果

原效果:差不多有个 3s 的白屏时间

20240505131518_rec_.gif

优化后:几乎是瞬间显示

20240505131301_rec_.gif

问题分析

渲染大量组件导致白屏问题主要因为:

  1. 大量的 DOM 操作:每个图表都需要创建新的 DOM 元素,这导致浏览器需要处理大量的元素插入和渲染任务,耗费大量计算资源。
  2. JavaScript 执行阻塞:Vue 的响应式机制和组件的初始化需要大量的 JavaScript 计算,这在大规模渲染时会阻塞 UI 线程。
  3. 资源密集的图表计算:每个 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 渲染大量的组件是一个有效的策略,特别是在处理大量数据可视化组件时。这种方法不仅改善了性能,还优化了用户的交互体验。