在Vue3+ECharts中图表tooltip不显示问题分析和解决方法
作者:爱看星星的猪_
一、问题复现
在开发一个展示“我行信贷五级分类变化趋势”的柱状图时,出现以下情况:

- 左侧示例图(预期效果):鼠标悬停在某个月份上时,会弹出包含“正常类”“关注类”“不良类”金额的详情浮窗。
- 右侧实际图(当前问题):虽然图表能正常渲染,但鼠标悬停没有任何反应,
tooltip完全不出现。
我已经设置了:
tooltip: {
trigger: 'axis',
confine: true,
axisPointer: { type: 'shadow' }
}并且数据结构也正确,为什么 tooltip 还是不显示?
二、问题所在
以下是导致问题的核心代码片段:
const myChart = ref(null);
const initChart = () => {
if (!classify.value) return;
myChart.value = echarts.init(classify.value); // ❌ 问题就在这里!
const seriesData = [];
// ... 后续处理数据并 setOption
};关键错误点分析
myChart是通过ref()创建的响应式引用。- 当执行
myChart.value = echarts.init(...)时,ECharts 的实例被赋值给了一个 Vue 响应式对象。 - Vue 会对这个实例进行 Proxy 包装,使其变成响应式代理对象。
- ECharts 内部依赖于对象的原始引用(如
===判断、事件绑定等),一旦被 Proxy 包裹,就会导致:
tooltip触发逻辑失效formatter函数不执行axisPointer无法正确定位
所以,尽管写了正确的 tooltip.trigger: 'axis',但由于 ECharts 实例被 Vue “污染”,它根本不知道该什么时候触发提示框!
三、修改方式:使用markRaw解决
修改后的代码(关键改动)
const myChart = ref(null);
const initChart = () => {
if (!classify.value) return;
myChart.value = markRaw(echarts.init(classify.value)); // ✅ 使用 markRaw 包裹
const seriesData = [];
// ... 后续处理数据并 setOption
};改动说明
| 原始代码 | 修改后 |
myChart.value = echarts.init(...) | myChart.value = markRaw(echarts.init(...)) |
markRaw() 是 Vue 提供的一个工具函数,作用是:标记某个值永远不要被转为响应式。
四、原理详解:为什么markRaw能解决问题?
1. Vue 的响应式机制
Vue 3 使用 Proxy 实现响应式系统。当你把一个对象赋值给 ref 或 reactive 时,Vue 会创建一个代理对象来拦截读写操作。
const obj = { a: 1 }
const reactiveObj = reactive(obj) // ← 现在是 Proxy 对象2. ECharts 的内部机制
ECharts 在初始化时会做以下事情:
- 绑定事件监听器(如
mouseover) - 存储 DOM 元素引用
- 使用
===比较对象是否相等(用于判断是否需要重新渲染) - 在
tooltip中通过instance引用获取配置
如果实例被 Proxy 包裹,这些比较和绑定就会失败。
3.markRaw的作用
const rawInstance = markRaw(echarts.init(dom))
这行代码告诉 Vue:“这个对象不需要被代理,请原封不动地传下去。”
这样,ECharts 就拿到了一个原始的、未被包装的实例,可以正常运行其内部逻辑。
五、修改后的影响 & 如何应对
正面影响
| 优势 | 说明 |
✅ tooltip 正常显示 | 鼠标悬停时弹出详细信息 |
✅ formatter 函数生效 | 可自定义提示内容 |
✅ axisPointer 正确工作 | 阴影指示器正常显示 |
| ✅ 图表性能提升 | 避免不必要的响应式追踪 |
潜在影响与解决方案
1.不能直接在模板中绑定myChart.value
因为 myChart.value 是 markRaw 的结果,不是响应式对象,所以:
<!-- ❌ 错误:不能直接在模板中使用 -->
<template>
<div>{{ myChart.value }}</div> <!-- 会报错或显示 undefined -->
</template>✅ 解决方案:只在 JS 中使用,不暴露到模板。
2.无法自动响应容器大小变化
如果你希望图表随容器缩放而自动重绘,需手动监听。
✅ 解决方案:使用 ResizeObserver 手动调用 resize():
const resizeObserver = ref(null)
onMounted(() => {
resizeObserver.value = new ResizeObserver(() => {
myChart.value?.resize()
})
resizeObserver.value.observe(classify.value)
})
onUnmounted(() => {
resizeObserver.value?.disconnect()
})3.生命周期管理仍需注意
即使用了 markRaw,也要记得销毁图表:
onUnmounted(() => {
myChart.value?.dispose()
})六、完整示例代码(修复版)
<template>
<el-card class="customer-risk-card">
<span class="title">我行信贷五级分类变化趋势(金额:万元)</span>
<div ref="classify" class="chart"></div>
</el-card>
</template>
<script setup>
import { ref, onMounted, onUnmounted, markRaw } from 'vue'
import * as echarts from 'echarts'
import RwmscenterApi from '@api/rwmscenter/rwmscenterApi.ts'
const props = defineProps({
params: Object,
})
const classify = ref(null)
const myChart = ref(null)
const chartData = ref({
xAxisData: [],
originData: {},
})
const resizeObserver = ref(null)
// 获取图表数据
const getCstRiskFiveGrade = async () => {
try {
const res = await RwmscenterApi.getCstRiskFiveGrade({
cstGycd: props.params.cstGycd,
cstNo: props.params.cstNo,
})
chartData.value.originData = res.body.data
initChart()
} catch (error) {
console.error('获取数据失败:', error)
}
}
// 初始化图表
const initChart = () => {
if (!classify.value) return
myChart.value = markRaw(echarts.init(classify.value)) // ✅ 关键:使用 markRaw
const seriesData = []
for (const key in chartData.value.originData) {
seriesData.push({
name: key,
type: 'bar',
stack: 'total',
label: { show: false },
emphasis: { focus: 'series' },
data: chartData.value.originData[key].map(item => item.value / 10000),
barWidth: 16,
})
}
const option = {
colors: [
{ type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: '#66DAA8' }, { offset: 1, color: '#fff' }] },
{ type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: '#A30559' }, { offset: 1, color: '#fff' }] },
{ type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: ?('FFBE5E'), { offset: 1, color: '#fff' }] },
{ type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: '#FA95E' }, { offset: 1, color: '#fff' }] },
{ type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: '#FFBE5E' }, { offset: 1, color: '#fff' }] },
],
tooltip: {
trigger: 'axis',
confine: true,
axisPointer: { type: 'shadow' },
formatter: function(params) {
const date = params[0].axisValue
let html = `<div style="padding: 8px; background: white; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); font-size: 12px;">`
html += `<strong>${date}</strong>
`
params.forEach(param => {
html += `<span style="color: ${param.color};">●</span> ${param.seriesName}: <strong>${param.value} 万元</strong>
`
})
html += '</div>'
return html
}
},
legend: { right: 'center' },
grid: { left: '3%', right: '4%', bottom: '8%', containLabel: true },
yAxis: { type: 'value', name: '', nameTextStyle: { color: '#999999', fontSize: 12 }, axisLabel: { color: '#666666', fontSize: 12 }, splitLine: { show: true, lineStyle: { color: '#EEEEEE' } } },
xAxis: { type: 'category', axisLine: { show: true, lineStyle: { color: '#F3F4F9' } }, axisTick: { show: false }, axisLabel: { color: '#666666', fontSize: 12 }, data: chartData.value.xAxisData },
series: seriesData
}
myChart.value.setOption(option)
}
// 监听窗口大小变化
const handlerResize = () => {
myChart.value?.resize()
}
onMounted(() => {
getCstRiskFiveGrade()
if (typeof ResizeObserver !== 'undefined') {
resizeObserver.value = new ResizeObserver(entries => handlerResize())
resizeObserver.value.observe(classify.value)
}
})
onBeforeUnmount(() => {
if (myChart.value) myChart.value.dispose()
if (resizeObserver.value) resizeObserver.value.disconnect()
})
</script>
<style lang="less" scoped>
.customer-risk-card {
.title {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.chart {
width: 100%;
height: 300px;
}
}
</style>总结:解决问题的完整路径
| 步骤 | 操作 | 目的 |
| 1️⃣ | 发现 tooltip 不显示 | 定位问题 |
| 2️⃣ | 查看代码发现 myChart.value = echarts.init(...) | 找到根源 |
| 3️⃣ | 使用 markRaw 包裹实例 | 防止 Vue 干扰 |
| 4️⃣ | 添加 formatter 自定义提示 | 实现期望效果 |
| 5️⃣ | 补充 resize 和 dispose | 完善生命周期管理 |
结论:当在 Vue 3 中使用 ECharts 时,若遇到 tooltip 无法触发、formatter 不执行等问题,绝大多数情况下是因为 ECharts 实例被 Vue 的响应式系统干扰了。
解决方案就是:使用 markRaw(echarts.init(...)),让实例保持“原生状态”。
以上就是在Vue3+ECharts中图表tooltip不显示问题分析和解决方法的详细内容,更多关于Vue3 ECharts图表tooltip不显示的资料请关注脚本之家其它相关文章!
