ResizeObserver 解决 echarts渲染不出来,内容宽度为 0的问题

【问题场景】 vue项目,封装了echarts相关的图表组件,在父组件中引入这些组件,图表宽度由父组件中的包裹元素决定,而这些包裹元素的宽度不固定,是按比例计算来的,页面刷新时,偶现部分图表渲染不出来,F12定位发现 这些图表的内容宽度是 0。

【原先的代码】

1、父组件

bash 复制代码
<template>
  <div class="chart-wrapper">
    <div class="chart-card margin-right-16">
      <PieChart v-bind="pieChart" />
    </div>
    <div class="chart-card margin-right-16">
      <LineChart v-bind="lineChart" />
    </div>
    <div class="chart-card">
      <BarChart v-bind="barChart" />
    </div>
  </div>
</template>

<script setup>
  import { ref, onMounted } from 'vue'
  import PieChart from '@/components/Charts/PieChart'
  import LineChart from '@/components/Charts/LineChart'
  import BarChart from '@/components/Charts/BarChart'
  
  const pieChart = ref(
    {
      id: 'pieChart',
      chartStyle: {
        height: '180px'
      }
    }
  )
  const fetchPieData = () => {
    ...
    pieChart.value = {
      id: 'pieChart',
      chartStyle: {
        height: '180px'
      },
      title: ...
      legend: ...
      series: ...
    }
  }

  const lineChart = 
  const fetchLineData = 

  const barChart = 
  const fetchBarData = 
  
  onMounted(() => {
    fetchPieData()
    fetchLineData()
    fetchBarData()
  })
</script>
  
<style lang="scss" scoped>
  .chart-wrapper {
    width: 100%;
    .chart-card {
      display: inline-block;
      width: calc((100% - 32px) / 3);
      height: 240px;
    }
    .margin-right-16 {
      margin-right: 16px;
    }
  }
</style>

2、echarts图表组件 BarChart.vue

bash 复制代码
<template>
  <div :id="id" :style="chartStyle" class="chart-contianer"></div>
</template>

<script setup>
import { onMounted, nextTick, watch } from 'vue'
import * as echarts from 'echarts'

const props = defineProps({
  id: {
    type: String,
    default: ''
  },
  chartStyle: {
    type: Object,
    default: () => ({})
  },
  xAxis: ...
  yAxis: ...
  series: ...
})

const drawChart = async () => {
  await nextTick()
  const container = document.getElementById(props.id)
  const chartsDom = echarts.init(container)
  container.removeAttribute('_echarts_instance_')
  const option = {
    grid: ...
    tooltip: ...
    legend: ...
    color: ...
    xAxis: ...
    yAxis: ...
    series: ...
  }
  chartsDom.setOption(option)
}

watch(() => props.seriesData, (newVal) => {
  if (newVal) drawChart()
}, { deep: true })

onMounted(() => {
  drawChart()
})

</script>

<style lang="scss" scoped>
  .chart-contianer {
    width: 100%;
    height: 100%;
  }
</style>

【错误的思考方向】 echarts组件的数据获取是在父组件的 onMounted 中进行的,父组件 DOM 节点挂载完成的时候子组件应该也挂载完成了,而且子组件中还有 watch 监听图表数据的变化可以重新渲染,不应该会出现获取不到容器元素的问题。

【原因分析】 其实echarts能渲染,表示容器元素是存在的,所以不是获取不到容器元素的问题,而是容器元素的宽度还未撑开。真正的原因是: 即使 DOM 挂载完成,浏览器的 layout/reflow 还未完成,导致子元素 size 还没确定。即 DOM 挂载完成不代表渲染完成。所以什么把数据获取放在 onMounted 中进行、获取数据之后在 nextTick 之后渲染图表,都没有用。

【解决方法】 监听容器元素的尺寸变化,即时重新渲染图表,这里可以用 ResizeObserver 。具体API参考 ResizeObserver

【修改子组件代码】

bash 复制代码
<template>
  <div :id="id" :style="chartStyle" class="chart-contianer"></div>
</template>

<script setup>
import { onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
import * as echarts from 'echarts'

...

// ------------------监听容器元素 start------------------
// 创建一个 ResizeObserver实例
const observer = new ResizeObserver(entries => {
  entries.forEach(entry => {
    if (entry?.target?.id === props.id) drawChart()
  })
})

onMounted(() => {
  // 监听指定元素
  observer.observe(document.getElementById(props.id))
  drawChart()
})

onBeforeUnmount(() => {
  observer.unobserve(document.getElementById(props.id))
})
// ------------------监听容器元素 end------------------
</script>

<style lang="scss" scoped>
  .chart-contianer {
    width: 100%;
    height: 100%;
  }
</style>
相关推荐
web打印社区2 分钟前
web-print-pdf:突破浏览器限制,实现专业级Web静默打印
前端·javascript·vue.js·electron·html
RFCEO22 分钟前
前端编程 课程十三、:CSS核心基础1:CSS选择器
前端·css·css基础选择器详细教程·css类选择器使用方法·css类选择器命名规范·css后代选择器·精准选中嵌套元素
烬头88211 小时前
React Native鸿蒙跨平台采用了函数式组件的形式,通过 props 接收分类数据,使用 TouchableOpacity实现了点击交互效果
javascript·react native·react.js·ecmascript·交互·harmonyos
Amumu121381 小时前
Vuex介绍
前端·javascript·vue.js
We་ct1 小时前
LeetCode 54. 螺旋矩阵:两种解法吃透顺时针遍历逻辑
前端·算法·leetcode·矩阵·typescript
2601_949809591 小时前
flutter_for_openharmony家庭相册app实战+相册详情实现
javascript·flutter·ajax
qq_177767371 小时前
React Native鸿蒙跨平台通过Animated.Value.interpolate实现滚动距离到动画属性的映射
javascript·react native·react.js·harmonyos
2601_949833391 小时前
flutter_for_openharmony口腔护理app实战+饮食记录实现
android·javascript·flutter
2601_949480061 小时前
【无标题】
开发语言·前端·javascript
css趣多多1 小时前
Vue过滤器
前端·javascript·vue.js