在Vue3+ECharts中圖表tooltip不顯示問題分析和解決方法
一、問題復(fù)現(xiàn)
在開發(fā)一個展示“我行信貸五級分類變化趨勢”的柱狀圖時,出現(xiàn)以下情況:

- 左側(cè)示例圖(預(yù)期效果):鼠標(biāo)懸停在某個月份上時,會彈出包含“正常類”“關(guān)注類”“不良類”金額的詳情浮窗。
- 右側(cè)實(shí)際圖(當(dāng)前問題):雖然圖表能正常渲染,但鼠標(biāo)懸停沒有任何反應(yīng),
tooltip完全不出現(xiàn)。
我已經(jīng)設(shè)置了:
tooltip: {
trigger: 'axis',
confine: true,
axisPointer: { type: 'shadow' }
}并且數(shù)據(jù)結(jié)構(gòu)也正確,為什么 tooltip 還是不顯示?
二、問題所在
以下是導(dǎo)致問題的核心代碼片段:
const myChart = ref(null);
const initChart = () => {
if (!classify.value) return;
myChart.value = echarts.init(classify.value); // ? 問題就在這里!
const seriesData = [];
// ... 后續(xù)處理數(shù)據(jù)并 setOption
};關(guān)鍵錯誤點(diǎn)分析
myChart是通過ref()創(chuàng)建的響應(yīng)式引用。- 當(dāng)執(zhí)行
myChart.value = echarts.init(...)時,ECharts 的實(shí)例被賦值給了一個 Vue 響應(yīng)式對象。 - Vue 會對這個實(shí)例進(jìn)行 Proxy 包裝,使其變成響應(yīng)式代理對象。
- ECharts 內(nèi)部依賴于對象的原始引用(如
===判斷、事件綁定等),一旦被 Proxy 包裹,就會導(dǎo)致:
tooltip觸發(fā)邏輯失效formatter函數(shù)不執(zhí)行axisPointer無法正確定位
所以,盡管寫了正確的 tooltip.trigger: 'axis',但由于 ECharts 實(shí)例被 Vue “污染”,它根本不知道該什么時候觸發(fā)提示框!
三、修改方式:使用markRaw解決
修改后的代碼(關(guān)鍵改動)
const myChart = ref(null);
const initChart = () => {
if (!classify.value) return;
myChart.value = markRaw(echarts.init(classify.value)); // ? 使用 markRaw 包裹
const seriesData = [];
// ... 后續(xù)處理數(shù)據(jù)并 setOption
};改動說明
| 原始代碼 | 修改后 |
myChart.value = echarts.init(...) | myChart.value = markRaw(echarts.init(...)) |
markRaw() 是 Vue 提供的一個工具函數(shù),作用是:標(biāo)記某個值永遠(yuǎn)不要被轉(zhuǎn)為響應(yīng)式。
四、原理詳解:為什么markRaw能解決問題?
1. Vue 的響應(yīng)式機(jī)制
Vue 3 使用 Proxy 實(shí)現(xiàn)響應(yīng)式系統(tǒng)。當(dāng)你把一個對象賦值給 ref 或 reactive 時,Vue 會創(chuàng)建一個代理對象來攔截讀寫操作。
const obj = { a: 1 }
const reactiveObj = reactive(obj) // ← 現(xiàn)在是 Proxy 對象2. ECharts 的內(nèi)部機(jī)制
ECharts 在初始化時會做以下事情:
- 綁定事件監(jiān)聽器(如
mouseover) - 存儲 DOM 元素引用
- 使用
===比較對象是否相等(用于判斷是否需要重新渲染) - 在
tooltip中通過instance引用獲取配置
如果實(shí)例被 Proxy 包裹,這些比較和綁定就會失敗。
3.markRaw的作用
const rawInstance = markRaw(echarts.init(dom))
這行代碼告訴 Vue:“這個對象不需要被代理,請原封不動地傳下去。”
這樣,ECharts 就拿到了一個原始的、未被包裝的實(shí)例,可以正常運(yùn)行其內(nèi)部邏輯。
五、修改后的影響 & 如何應(yīng)對
正面影響
| 優(yōu)勢 | 說明 |
? tooltip 正常顯示 | 鼠標(biāo)懸停時彈出詳細(xì)信息 |
? formatter 函數(shù)生效 | 可自定義提示內(nèi)容 |
? axisPointer 正確工作 | 陰影指示器正常顯示 |
| ? 圖表性能提升 | 避免不必要的響應(yīng)式追蹤 |
潛在影響與解決方案
1.不能直接在模板中綁定myChart.value
因為 myChart.value 是 markRaw 的結(jié)果,不是響應(yīng)式對象,所以:
<!-- ? 錯誤:不能直接在模板中使用 -->
<template>
<div>{{ myChart.value }}</div> <!-- 會報錯或顯示 undefined -->
</template>? 解決方案:只在 JS 中使用,不暴露到模板。
2.無法自動響應(yīng)容器大小變化
如果你希望圖表隨容器縮放而自動重繪,需手動監(jiān)聽。
? 解決方案:使用 ResizeObserver 手動調(diào)用 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()
})六、完整示例代碼(修復(fù)版)
<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)
// 獲取圖表數(shù)據(jù)
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('獲取數(shù)據(jù)失敗:', error)
}
}
// 初始化圖表
const initChart = () => {
if (!classify.value) return
myChart.value = markRaw(echarts.init(classify.value)) // ? 關(guān)鍵:使用 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)
}
// 監(jiān)聽窗口大小變化
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>總結(jié):解決問題的完整路徑
| 步驟 | 操作 | 目的 |
| 1?? | 發(fā)現(xiàn) tooltip 不顯示 | 定位問題 |
| 2?? | 查看代碼發(fā)現(xiàn) myChart.value = echarts.init(...) | 找到根源 |
| 3?? | 使用 markRaw 包裹實(shí)例 | 防止 Vue 干擾 |
| 4?? | 添加 formatter 自定義提示 | 實(shí)現(xiàn)期望效果 |
| 5?? | 補(bǔ)充 resize 和 dispose | 完善生命周期管理 |
結(jié)論:當(dāng)在 Vue 3 中使用 ECharts 時,若遇到 tooltip 無法觸發(fā)、formatter 不執(zhí)行等問題,絕大多數(shù)情況下是因為 ECharts 實(shí)例被 Vue 的響應(yīng)式系統(tǒng)干擾了。
解決方案就是:使用 markRaw(echarts.init(...)),讓實(shí)例保持“原生狀態(tài)”。
以上就是在Vue3+ECharts中圖表tooltip不顯示問題分析和解決方法的詳細(xì)內(nèi)容,更多關(guān)于Vue3 ECharts圖表tooltip不顯示的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中動態(tài)修改animation效果無效問題詳情
這篇文章主要介紹了vue中動態(tài)修改animation效果無效問題詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-06-06
使用vue ant design分頁以及表格分頁改為中文問題
這篇文章主要介紹了使用vue ant design分頁以及表格分頁改為中文問題,具有很好的參考價值,希望對大家有所幫助。2023-04-04
vue3使用SSE實(shí)現(xiàn)前端全局事件通訊方式
文章介紹了使用Vue3和VueUse實(shí)現(xiàn)基于SSE(Server-Sent Events)的全局事件通訊系統(tǒng),SSE具有單向通信、基于HTTP、自動重連和輕量級等特點(diǎn),適合實(shí)時性要求高的應(yīng)用,實(shí)現(xiàn)方案包括訂閱消息、取消訂閱和關(guān)閉連接等功能2025-10-10
vue實(shí)現(xiàn)自定義日期組件功能的實(shí)例代碼
這篇文章主要介紹了vue自定義日期組件的實(shí)現(xiàn)代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11
vue+element-ui監(jiān)聽滾動實(shí)現(xiàn)錨點(diǎn)定位方式(雙向),錨點(diǎn)問題
這篇文章主要介紹了vue+element-ui監(jiān)聽滾動實(shí)現(xiàn)錨點(diǎn)定位方式(雙向),錨點(diǎn)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07
前端實(shí)現(xiàn)Vue組件頁面跳轉(zhuǎn)的多種方式小結(jié)
這篇文章主要為大家詳細(xì)介紹了前端實(shí)現(xiàn)Vue組件頁面跳轉(zhuǎn)的多種方式,文中的示例代碼講解詳細(xì),具有一定的參考價值,有需要的小伙伴可以了解下2024-02-02
Vue3實(shí)現(xiàn)動態(tài)導(dǎo)入Excel表格數(shù)據(jù)的方法詳解
在開發(fā)工作過程中,我們會遇到各種各樣的表格數(shù)據(jù)導(dǎo)入,動態(tài)數(shù)據(jù)導(dǎo)入可以減少人為操作,減少出錯。本文為大家介紹了Vue3實(shí)現(xiàn)動態(tài)導(dǎo)入Excel表格數(shù)據(jù)的方法,需要的可以參考一下2022-11-11
詳解Vue中Computed與watch的用法與區(qū)別
這篇文章主要介紹了Vue中computed和watch的使用與區(qū)別,文中通過示例為大家進(jìn)行了詳細(xì)講解,對Vue感興趣的同學(xué),可以學(xué)習(xí)一下2022-04-04

