基于Vue3+ECharts實現(xiàn)動態(tài)地圖切換與平滑過渡的動畫效果
本文將詳細(xì)解析如何使用 Vue3 和 ECharts 實現(xiàn)市級地圖展示功能,并添加地圖切換時的平滑過渡動畫效果。
- 使用 ECharts 的
universalTransition實現(xiàn)地圖切換動畫 - 通過 Canvas 動態(tài)處理圖像實現(xiàn)區(qū)域高亮效果
- 設(shè)計響應(yīng)式布局確保地圖適應(yīng)不同屏幕尺寸
- 實現(xiàn)層級導(dǎo)航和交互反饋提升用戶體驗
功能概述

本組件實現(xiàn)了一個具有以下特性的地圖展示系統(tǒng):
- 展示河南省周口市行政區(qū)劃地圖
- 點擊地圖區(qū)域可切換到區(qū)縣級別視圖
- 地圖切換時帶有平滑的過渡動畫
- 自定義地圖樣式和交互效果
- 響應(yīng)式布局適應(yīng)不同屏幕尺寸
技術(shù)棧
- Vue3(Composition API)
- ECharts 5
- TypeScript
- Canvas 圖像處理
核心功能實現(xiàn)
1. 組件結(jié)構(gòu)與依賴
<template>
<div id="mapChart" class="mt-4 w-full" ref="mapChartRef"></div>
</template>
<script setup lang="ts">
import * as echarts from 'echarts';
import mapBgImgSrc from '@/assets/images/map-bg.png';
import geoData from '@/assets/jeo/jeoMap.json';
import { ref, onMounted, nextTick } from 'vue';
// 組件引用和數(shù)據(jù)
const mapChartRef = ref<HTMLElement | null>(null);
const selectDistrictVal = ref('zhoukou'); // 當(dāng)前選中的地區(qū)
let myChart: echarts.ECharts | null = null; // ECharts實例
2. 動態(tài)創(chuàng)建遮罩背景圖
// 創(chuàng)建帶遮罩的背景圖像(用于高亮區(qū)域)
function createMaskedBgImg(src: string, maskColor = 'rgba(0,0,0,0.1)'): Promise<string> {
return new Promise((resolve) => {
const img = new Image();
img.src = src;
const canvas = document.createElement('canvas');
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d')!;
// 繪制原始圖像
ctx.drawImage(img, 0, 0);
// 添加遮罩層
ctx.fillStyle = maskColor;
ctx.fillRect(0, 0, img.width, img.height);
resolve(canvas.toDataURL());
};
img.onerror = () => resolve(src); // 失敗時返回原圖
});
}
3. 地圖配置生成器
// 生成ECharts地圖配置
const getMapOption = (mapType = 'zhoukou', maskedBgImg = mapBgImgSrc) => {
// 從GeoJSON數(shù)據(jù)中提取當(dāng)前區(qū)域
const feature = geoData.features.find(
(f: any) => f.properties.name === mapType
);
const mapGeoData = mapType === 'zhoukou'
? geoData
: {
type: 'FeatureCollection',
features: feature ? [feature] : []
};
// 注冊地圖數(shù)據(jù)
echarts.registerMap('zhoukou', mapGeoData as any);
return {
// 動畫配置
animation: true,
animationDuration: 800,
animationEasing: 'cubicOut' as any,
animationDurationUpdate: 800,
animationEasingUpdate: 'cubicOut' as any,
// 標(biāo)題
title: {
top: 10,
text: mapType === 'zhoukou' ? '河南省 - 周口市' : `周口市 - ${mapType}`,
x: 'center',
textStyle: {
color: '#2564AD',
fontWeight: 600,
fontSize: 16
}
},
// 地理坐標(biāo)系配置
geo: {
map: 'zhoukou',
aspectScale: 0.8,
layoutCenter: ['50.6%', '51%'],
layoutSize: '91.5%',
roam: false,
z: 0,
itemStyle: {
areaColor: '#2564AD',
borderColor: '#2564AD',
borderWidth: 1
}
},
// 地圖系列
series: [{
type: 'map',
map: 'zhoukou',
universalTransition: {
enabled: true,
divideShape: 'clone' // 關(guān)鍵:啟用形狀分割動畫
},
zoom: 1.2,
itemStyle: {
areaColor: { image: mapBgImgSrc, repeat: 'repeat' },
borderColor: '#80AACC',
borderWidth: 2
},
emphasis: { // 鼠標(biāo)懸停樣式
itemStyle: {
areaColor: { image: maskedBgImg, repeat: 'repeat' },
borderColor: '#80AACC'
},
label: { color: '#409eff', fontWeight: 'bold' }
},
select: { // 選中區(qū)域樣式
itemStyle: {
areaColor: { image: maskedBgImg, repeat: 'repeat' },
borderColor: '#80AACC'
},
label: { color: '#409eff', fontWeight: 'bold' }
},
label: { // 區(qū)域標(biāo)簽
show: true,
color: '#80AACC',
fontWeight: 'bold'
}
}]
};
};
4. 地圖初始化與交互
// 初始化地圖
const initMap = async () => {
if (!mapChartRef.value) return;
// 創(chuàng)建ECharts實例
myChart = echarts.init(mapChartRef.value);
// 創(chuàng)建遮罩背景圖
const maskedBgImg = await createMaskedBgImg(
mapBgImgSrc,
'rgba(0,0,0,0.1)'
);
// 設(shè)置初始配置
myChart.setOption(getMapOption('zhoukou', maskedBgImg), false);
// 添加點擊事件處理
myChart.on('click', function (e: any) {
if (!e.name || typeof e.name !== 'string') return;
// 只有在地級市視圖時才能點擊進入?yún)^(qū)縣
if (selectDistrictVal.value === 'zhoukou') {
myChart.setOption(getMapOption(e.name, maskedBgImg), false);
selectDistrictVal.value = e.name;
}
});
};
// 響應(yīng)式調(diào)整地圖尺寸
const setMapView = async () => {
await nextTick();
if (mapChartRef.value?.parentNode) {
const parentHeight = mapChartRef.value.parentNode.offsetHeight;
const siblingHeight = mapChartRef.value.previousSibling?.offsetHeight || 0;
mapChartRef.value.style.height = `${parentHeight - siblingHeight - 16}px`;
}
// 窗口大小變化時重繪
window.addEventListener('resize', () => {
myChart?.resize();
});
};
// 組件掛載時初始化
onMounted(async () => {
await setMapView();
initMap();
});
// 暴露外部控制方法
defineExpose({
getCurrentDistrict: () => selectDistrictVal.value,
setDistrict: async (district: string) => {
selectDistrictVal.value = district;
const maskedBgImg = await createMaskedBgImg(
mapBgImgSrc,
'rgba(0,0,0,0.1)'
);
myChart?.setOption(getMapOption(district, maskedBgImg), false);
}
});
</script>
關(guān)鍵技術(shù)與優(yōu)化點
1. 平滑過渡動畫實現(xiàn)
通過以下配置實現(xiàn)地圖切換時的平滑動畫效果:
// 關(guān)鍵動畫配置
universalTransition: {
enabled: true,
divideShape: 'clone' // 形狀分割動畫
},
animationDuration: 800,
animationEasing: 'cubicOut',
animationDurationUpdate: 800,
animationEasingUpdate: 'cubicOut'
universalTransition 是 ECharts 5 引入的強大功能,它允許在數(shù)據(jù)更新時自動生成過渡動畫,特別適合地理區(qū)域的切換。
2. Canvas 動態(tài)圖像處理
使用 Canvas 動態(tài)生成帶遮罩的背景圖,實現(xiàn)區(qū)域高亮效果:
// 創(chuàng)建帶遮罩的背景圖 ctx.drawImage(img, 0, 0); // 繪制原圖 ctx.fillStyle = maskColor; // 設(shè)置遮罩顏色 ctx.fillRect(0, 0, img.width, img.height); // 繪制遮罩層
3. 響應(yīng)式布局處理
// 動態(tài)計算地圖容器高度
const parentHeight = mapChartRef.value.parentNode.offsetHeight;
const siblingHeight = mapChartRef.value.previousSibling?.offsetHeight || 0;
mapChartRef.value.style.height = `${parentHeight - siblingHeight - 16}px`;
// 監(jiān)聽窗口大小變化
window.addEventListener('resize', () => {
myChart?.resize();
});
4. 交互體驗優(yōu)化
- 層級導(dǎo)航:只能從市級視圖進入?yún)^(qū)縣視圖,防止無限深入
- 視覺反饋:懸停和選中狀態(tài)有明顯樣式變化
- 標(biāo)題動態(tài)更新:根據(jù)當(dāng)前視圖級別顯示不同標(biāo)題
- 性能優(yōu)化:使用
false參數(shù)避免不必要的重繪
myChart.setOption(getMapOption(e.name, maskedBgImg), false);
使用示例
在父組件中使用地圖組件
<template>
<div class="container">
<h1>河南省行政區(qū)劃地圖</h1>
<div class="controls">
<button @click="backToCity">返回市級視圖</button>
<select v-model="selectedDistrict" @change="changeDistrict">
<option value="zhoukou">周口市</option>
<option value="taikang">太康縣</option>
<option value="huaiyang">淮陽縣</option>
<!-- 其他區(qū)縣選項 -->
</select>
</div>
<MapComponent ref="mapRef" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MapComponent from './MapComponent.vue';
const mapRef = ref();
const selectedDistrict = ref('zhoukou');
// 切換區(qū)縣
const changeDistrict = () => {
mapRef.value.setDistrict(selectedDistrict.value);
};
// 返回市級視圖
const backToCity = () => {
selectedDistrict.value = 'zhoukou';
mapRef.value.setDistrict('zhoukou');
};
</script>
總結(jié)
以上就是基于Vue3+ECharts實現(xiàn)動態(tài)地圖切換與平滑過渡的動畫效果的詳細(xì)內(nèi)容,更多關(guān)于Vue3 ECharts地圖切換與平滑過渡的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
對Vue table 動態(tài)表格td可編輯的方法詳解
今天小編就為大家分享一篇對Vue table 動態(tài)表格td可編輯的方法詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
vue3使用useDialog實現(xiàn)對話框的示例代碼
在日常開發(fā)中,彈窗是常見的一個功能,本文主要介紹了vue3使用useDialog實現(xiàn)對話框的示例代碼,具有一定的參考價值,感興趣的可以了解一下2024-01-01
vue3+vite+tdesign實現(xiàn)日歷式可編輯的排課班表填寫功能
本文介紹了如何使用Vue3和tdesign實現(xiàn)一個日歷式、可編輯的排班填寫功能,開發(fā)過程中面臨了年份和月份下拉框的實現(xiàn)、周期顯示以及可編輯日歷的樣式和數(shù)據(jù)獲取等挑戰(zhàn),感興趣的朋友一起看看吧2025-01-01
vite+vue3中require?is?not?defined問題及解決
這篇文章主要介紹了vite+vue3中require?is?not?defined問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05

