最近在开发一个省级行政区划可视化项目时,遇到了一个典型的前端图层交互问题。项目需要实现以下核心功能:
这个需求的技术难点在于:ECharts默认使用Canvas渲染,会阻挡下层SVG的鼠标事件。如果简单地将ECharts图层设置为事件穿透(pointer-events: none),虽然可以点击到底层地图,但会导致Tooltip无法触发。
我们采用分层设计的思路:
html复制<template>
<!-- 底层:SVG地图容器 -->
<div class="svg_wrap" id="svg_wrap">
<SvgProvince class="svg_map"></SvgProvince>
</div>
<!-- 上层:ECharts容器 -->
<div class="chart_wrap"
:style="{width:chartWidth,height:chartHeight}"
id="idHy"></div>
</template>
关键CSS设置:
css复制.chart_wrap {
position: absolute;
top: 0;
left: 0;
pointer-events: none; /* 允许鼠标事件穿透 */
}
为了实现Tooltip显示而同时不阻挡点击事件,我们采用以下策略:
javascript复制const option = {
xAxis: { show: false },
yAxis: { show: false },
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
enterable: true,
formatter: function(params) {
// 自定义Tooltip内容
return `<div class="tooltip-content">
<p>${params.name}</p>
<p>数量:${params.value}</p>
</div>`;
}
},
series: [
{
type: "lines",
coordinateSystem: "cartesian2d",
lineStyle: { width: 10, color: "#00ff7e" },
data: normalSeriesData
}
]
};
关键点:必须设置triggerOn为'mousemove',这样dispatchAction才能正确触发Tooltip
javascript复制const chartDom = document.getElementById('idHy');
const parentDom = chartDom.parentElement;
parentDom.addEventListener('mousemove', (e) => {
const rect = chartDom.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
chart.dispatchAction({
type: 'showTip',
position: [mouseX, mouseY],
x: mouseX,
y: mouseY
});
});
这里有几个技术细节需要注意:
连接线数据需要转换为ECharts需要的格式:
javascript复制chartData.forEach((item) => {
let { LINE_NUM, A_CITY, Z_CITY } = item;
let startPoint = cityPosition[A_CITY];
let endPoint = cityPosition[Z_CITY];
normalSeriesData.push({
name: `${A_CITY}-${Z_CITY}`,
value: LINE_NUM,
coords: [startPoint, endPoint]
});
});
可能原因:
解决方案:
javascript复制// 考虑页面滚动的影响
const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
const mouseX = e.clientX + scrollX - rect.left;
const mouseY = e.clientY + scrollY - rect.top;
javascript复制import { throttle } from 'lodash';
parentDom.addEventListener('mousemove', throttle((e) => {
// 事件处理逻辑
}, 50));
javascript复制series: [{
type: 'map',
map: 'provinceName',
// ...其他配置
}]
在移动设备上需要考虑:
javascript复制// 同时监听touch和mouse事件
['mousemove', 'touchmove'].forEach(event => {
parentDom.addEventListener(event, (e) => {
// 统一处理逻辑
});
});
以下是整合后的Vue组件实现:
javascript复制<template>
<div class="map-container">
<div class="svg_wrap" id="svg_wrap">
<SvgProvince @click="handleMapClick" />
</div>
<div class="chart_wrap"
:style="{width: chartWidth, height: chartHeight}"
id="idHy"
ref="chart"></div>
</div>
</template>
<script>
import { throttle } from 'lodash';
export default {
data() {
return {
chartWidth: '100%',
chartHeight: '100%',
cityPosition: {
'郑州': ['30', '57'],
// 其他城市坐标...
}
}
},
mounted() {
this.initSize();
this.initChart();
window.addEventListener('resize', this.handleResize);
},
methods: {
initSize() {
const svgWrap = document.getElementById('svg_wrap');
const style = getComputedStyle(svgWrap);
this.chartWidth = style.width;
this.chartHeight = style.height;
},
initChart() {
const chart = echarts.init(this.$refs.chart);
const option = {
// ...同上文option配置
};
chart.setOption(option);
this.setupTooltip(chart);
},
setupTooltip(chart) {
const chartDom = this.$refs.chart;
const parentDom = chartDom.parentElement;
const handler = throttle((e) => {
const rect = chartDom.getBoundingClientRect();
const scrollX = window.pageXOffset || document.documentElement.scrollLeft;
const scrollY = window.pageYOffset || document.documentElement.scrollTop;
const mouseX = e.clientX + scrollX - rect.left;
const mouseY = e.clientY + scrollY - rect.top;
chart.dispatchAction({
type: 'showTip',
position: [mouseX, mouseY]
});
}, 50);
['mousemove', 'touchmove'].forEach(event => {
parentDom.addEventListener(event, handler);
});
},
handleMapClick(event) {
// 处理地图点击事件
console.log('地图被点击', event);
},
handleResize: throttle(function() {
this.initSize();
this.$refs.chart && echarts.getInstanceByDom(this.$refs.chart).resize();
}, 300)
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
}
}
</script>
<style>
.map-container {
position: relative;
width: 100%;
height: 100%;
}
.svg_wrap {
width: 100%;
height: 100%;
}
.chart_wrap {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
</style>
javascript复制const monitor = () => {
let fps = 0;
requestAnimationFrame(function loop() {
fps++;
requestAnimationFrame(loop);
});
setInterval(() => {
console.log(`当前FPS: ${fps}`);
fps = 0;
}, 1000);
};
javascript复制watch: {
chartData(newVal) {
const chart = echarts.getInstanceByDom(this.$refs.chart);
// 更新数据逻辑...
chart.setOption(updatedOption);
}
}
javascript复制methods: {
toggleTheme() {
const theme = this.darkMode ? 'dark' : 'light';
echarts.dispose(this.$refs.chart);
this.chart = echarts.init(this.$refs.chart, theme);
this.initChart();
}
}
在实际项目中,这种图层交互方案已经成功应用在多个省级数据可视化平台。通过这种方式,我们既保留了底层地图的交互能力,又实现了上层数据可视化的信息展示需求。