问题:点击tab切换echarts,图表高度丢失
组件:
element ui
中的el-tab
、echarts
实例图
此处只做说明,图表尺寸丢失 切换tab之后
报错

现象分析
貌似在echarts初始化js执行的时候,页面的尺寸还未被渲染好,所以会出现echarts内组件拿不到节点宽度和高度。
原因分析
-
ECharts 容器的尺寸在 tab 切换时没有正确更新
在 Vue 中,
<el-tab-pane>
切换时,尚未激活的 tab 中的 DOM 元素不会渲染,导致它们的尺寸为0
。当你切换到该 tab 后,ECharts 尝试渲染时,容器的宽高可能为零,从而导致图表无法正确渲染。 -
ECharts 没有重新调整大小
由于
el-tabs
的懒加载特性,tab
中的内容在激活之前并不会渲染,所以ECharts
初始化时容器的尺寸是不可用的。即使激活了对应的tab
,ECharts
也没有感知到容器尺寸的变化,图表无法自动适应新的容器尺寸。
参考文章
代码
- 父组件
js
<template>
<div class="container">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="类型一" name="1">
<div class="chart-container">
<PieChart :data="oneData" ref="onePieRef" />
<div class="chart-box" ref="oneBarRef"></div>
</div>
</el-tab-pane>
<el-tab-pane label="类型二" name="2">
<div class="chart-container">
<PieChart :data="twoData" ref="twoPieRef" />
<div class="chart-box" ref="twoBarRef"></div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
<script setup>
...
</script>
<style lang="scss" scoped>
.container {
margin: 10px;
padding: 20px;
.chart-container {
width: 100%;
height: 50vh;
display: flex;
justify-content: space-between;
align-items: center;
}
.chart-box {
width: 50%;
height: 100%;
}
}
</style>
- 子组件
js
<template>
<div class="pie" ref="pieRef"></div>
</template>
<script setup>
...
</script>
<style lang="scss" scoped>
.pie {
width: 50%;
height: 100%;
}
</style>
解决方案
- 父组件和子组件都使用setTimeout函数延迟执行(最终解决版)
- 父组件
js
<script setup>
......
function initChart(val) {
...
setTimeout(() => {
myChart.value.resize(); // 延迟到浏览器完成 DOM 渲染之后再调用
}, 0);
}
// 监听 tab 的点击切换
function handleClick(tab) {
activeName.value = tab.paneName;
nextTick(() => {
// 使用 `setTimeout(..., 100)` 是为了让 DOM **彻底"稳定"**下来
//(Element Plus 的 `<el-tab-pane>` 内部有动画渲染或延迟渲染);
setTimeout(() => {
initChart(activeName.value);
// 根据当前选中的 tab 切换更新不同的饼图
if (activeName.value === "1" && onePieRef.value) {
onePieRef.value.resize();
} else if (activeName.value === "2" && twoPieRef.value) {
twoPieRef.value.resize();
}
}, 100);
});
}
</script>
- 子组件
js
<script setup>
watch(
() => props.data,
(newVal, oldVal) => {
nextTick(() => {
// DOM 渲染完再去初始化饼图
setTimeout(() => {
initPieChart();
}, 100);
});
},
{
immediate: true, // 组件初始化阶段就执行一次
deep: true,
}
);
let myChart = null;
function initPieChart() {
myChart = echarts.init(pieRef.value);
myChart.setOption(options.value, true);
setTimeout(() => {
myChart.resize();
}, 0);
}
// 对外提供的 resize 方法
function resize() {
if (myChart) myChart.resize();
}
// 向父组件暴露方法
defineExpose({
resize,
});
</script>
- 可以固定
echarts
容器的尺寸-(可解决,但是尺寸无法适配)
- 父组件
js
<template>
<div class="chart-box" ref="oneBarRef" :style={'width:500px;height:500px}'></div>
<div class="chart-box" ref="twoBarRef" :style={'width:500px;height:500px}'></div>
</template>
- 可以在
el-tab-pane
标签上添加lazy
-(增加之后问题未解决,可以尝试)
- element plus 官方文档
js
<template>
<div class="container">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="类型一" name="1" lazy> ...</el-tab-pane>
<el-tab-pane label="类型二" name="2" lazy>...</el-tab-pane>
</el-tabs>
</div>
</template>
- 可以在
echarts
容器上添加v-if
-(增加之后问题未解决,可以尝试)
js
<template>
<div class="container">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="类型一" name="1">
<div class="chart-container" v-if = "activeName === '1'"> // 这里
<PieChart :data="oneData" ref="onePieRef" />
<div class="chart-box" ref="oneBarRef"></div>
</div>
</el-tab-pane>
<el-tab-pane label="类型二" name="2">
<div class="chart-container" v-if = "activeName === '2'"> // 这里
<PieChart :data="twoData" ref="twoPieRef" />
<div class="chart-box" ref="twoBarRef"></div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</template>
- js中通过父元素的宽度重新给容器赋宽度-(麻烦-未尝试,这里仅提供思路)
其他警告⚠️及解决方案
警告 xxx.vue:198 [ECharts] There is a chart instance already initialized on the dom
说明 说明在对同一个 DOM 元素重复调用了 echarts.init(domEl)
,而这个 DOM 上已经有一个 ECharts 实例。
根本原因 在切换 el-tab
或更新图表时,没有先调用 dispose()
就重新 init()
了,所以 ECharts 报错说该 DOM 已经被初始化过。
解决方案
js
function initChart(val) {
if (myChart.value) {
myChart.value.dispose(); // 销毁图表实例
}
......
}