前言
做大屏的时候常有一个地图的功能需求,地图经常需要能做到对地图进行缩放拖拽等功能
但是地图要做好看,就必须要对地图进行叠加图层,叠加之后对地图进行缩放等功能就会出现只能缩放一个图层的问题
效果图展示
目标效果
- ✅效果一:地图的叠加效果
- ✅效果二:地图叠加图层情况下进行方法缩小,图层不乱
- ✅效果三:地图拖拽效果
- ✅效果四:地图根据经纬度标点位置
效果实现思路
关闭地图原本Echarts的缩放和拖放的功能。 放大缩小转为对Echarts容器div块的放大缩小
使用技术栈
使用技术 | 官网 or github |
---|---|
vue^3.2 | cn.vuejs.org/ |
echarts^5.4.3 | echarts.apache.org/zh/index.ht... |
geo地图json获取 | geojson.hxkj.vip/ |
项目整体文件
一、下载地图JSON
到geojson.hxkj.vip/ 选择你需要地图json
二、安装Echarts
安装echarts插件
npm install echarts
渲染Echarts地图数据
js
<script lang="ts" setup>
import * as echarts from "echarts";
import texture from "../../assets/image/bg.png";
/**
* 地图坐标数据
* name:地名
* value:经纬坐标
*/
const customerBatteryCityData = ref([
{ name: "阳江市", value: [111.777009756, 21.9715173045] },
{ name: "茂名市", value: [110.931245331, 21.9682257188] },
{ name: "广州市", value: [113.507649675, 23.3200491021] },
{ name: "河源市", value: [114.913721476, 23.9572508505] },
{ name: "湛江市", value: [110.165067263, 21.2574631038] },
{ name: "潮州市", value: [116.830075991, 23.7618116765] },
{ name: "东莞市", value: [113.863433991, 22.9430238154] },
{ name: "深圳市", value: [114.025973657, 22.5960535462] },
{ name: "清远市", value: [113.040773349, 23.9984685504] },
{ name: "韶关市", value: [113.594461107, 24.8029603119] },
{ name: "云浮市", value: [111.750945959, 22.9379756855] },
{ name: "惠州市", value: [114.410658089, 23.1135398524] },
{ name: "汕头市", value: [116.588650288, 23.2839084533] },
{ name: "揭阳市", value: [116.079500855, 23.3479994669] },
{ name: "中山市", value: [113.422060021, 22.5451775145] },
{ name: "肇庆市", value: [112.379653837, 23.5786632829] },
{ name: "珠海市", value: [113.262447026, 22.1369146461] },
{ name: "汕尾市", value: [115.572924289, 22.9787305002] },
{ name: "江门市", value: [112.678125341, 22.2751167835] },
{ name: "佛山市", value: [113.034025635, 23.0350948405] },
{ name: "梅州市", value: [116.126403098, 24.3045870606] },
]);
// Echarts地图容器初始化
const myChart = echarts.init(document.getElementById("main"));
// 引入地图json的URL
const uploadedDataURL = "../../src/assets/JSON/MeiZhou.geoJson";
// 引入地图json
fetch(uploadedDataURL)
.then((response) => response.json())
.then((geoJson) => {
// 注册地图
echarts.registerMap("guangdong", geoJson);
const option = {
// tip悬浮提示框
tooltip: {
//格式化内容,返回为空,地图组件不起作用,得在地图组件重新定义
formatter: () => "",
textStyle: {
color: "#fff",
},
},
// 地图叠加图层的方式
geo: [
{
map: "guangdong",
aspectScale: 0.9,
roam: false, // 是否允许缩放
zoom: 1, // 默认显示级别
layoutSize: "95%",
// 地图的偏移量
layoutCenter: ["55%", "48%"],
itemStyle: {
normal: {
borderColor: "#32B1CA",
borderWidth: 1,
// shadowColor: "#097D94",
// shadowOffsetY: 0,
// shadowBlur: 120,
areaColor: {
//图片纹理渲染,背景框要和Echarts容器的大小一致大小,然后调整地图在图片中的位置(ps:我的容器大小:1440*800)
// 详细的去看看官网的纹理渲染
image: texture,
repeat: "no-repeat",
},
},
},
label: {
show: true,
size: 20,
color: "#fff",
},
zlevel: -1,
silent: true,
},
{
map: "guangdong",
aspectScale: 0.9,
roam: false, // 是否允许缩放
zoom: 1, // 默认显示级别
layoutSize: "95%",
layoutCenter: ["55%", "49%"],
itemStyle: {
normal: {
borderColor: "#0BA3C1",
borderWidth: 1,
// shadowColor: "#097D94",
// shadowOffsetY: 0,
// shadowBlur: 120,
areaColor: "#0B869E",
},
},
zlevel: -3,
silent: true,
},
{
map: "guangdong",
aspectScale: 0.9,
roam: false, // 是否允许缩放
zoom: 1, // 默认显示级别
layoutSize: "95%",
layoutCenter: ["55%", "50%"],
itemStyle: {
normal: {
borderColor: "#097D94",
borderWidth: 1,
areaColor: "#086375",
},
},
zlevel: -4,
silent: true,
},
{
map: "guangdong",
aspectScale: 0.9,
roam: false, // 是否允许缩放
zoom: 1, // 默认显示级别
layoutSize: "95%",
layoutCenter: ["55%", "51%"],
itemStyle: {
// areaColor: '#005DDC',
areaColor: "#05414D",
borderColor: "#056679",
borderWidth: 1,
// shadowColor: "#2C99F6",
// shadowOffsetY: 0,
// shadowBlur: 120,
},
zlevel: -5,
silent: true,
},
],
series: [
{
type: "scatter",
coordinateSystem: "geo",
rippleEffect: {
number: 2,
scale: 3,
brushType: "stroke",
},
itemStyle: {
color: "#F56828",
zlevel: 333,
},
// icon
symbol:PvStop,
symbolSize: [44,63],
// 提示框
tooltip: {
formatter({ data }: any) {
return `地点位于:${data.name}`;
},
backgroundColor: "rgba(232,85,160,0.5)",
},
label: {
show: true,
position: "bottom",
color: "#ffffff",
formatter({ data }: any) {
return `${data.name}`;
},
zlevel: 100,
},
data:customerBatteryCityData.value
},
],
};
// 挂在地图
myChart.setOption(option);
// 地图点击事件
myChart.on("click", function (params) {
const { data }: any = params;
router.push(`station/${data.stationCode}`);
});
})
.catch((error) => console.error("Error:", error));
<script>
<template>
<div id="main"></div>
</template>
三、放大缩小div方法
然后给外层的main-box的div添加放大缩小方法
这个方法会根据以鼠标位置为中心对div的进行放大缩小
js
onMounted(() => {
const useZoom = (
el: HTMLElement,
callback = (translateData: { x: number; y: number }, scale: number) => {}
) => {
el.style.transformOrigin = "0 0";
/** 缩放的比例 */
let scale = 1;
/** 平移的距离 */
let translateData = { x: 0, y: 0 };
const { width, height } = el.getBoundingClientRect();
/** 重置数据, 并触发回调更新元素 */
const reset = () => {
scale = 1;
translateData = { x: 0, y: 0 };
callback(translateData, scale);
};
const wheelZoom = (event: any) => {
let _scale = scale;
_scale += event.deltaY > 0 ? -0.09 : 0.1;
if (_scale < 0.3) return;
let _translateData = distanceMovedZoom(event, scale, _scale);
scale = _scale;
// 需要移动的距离 = 已经移动的距离 + 需要再次移动的距离
translateData.x += _translateData.x;
translateData.y += _translateData.y;
callback(translateData, scale);
};
/** 计算每次需要移动的距离 */
const distanceMovedZoom = (
{ offsetX, offsetY }: any,
oldScale: number,
newScale: number
) => {
const newWidth = width * newScale;
const newHeight = height * newScale;
const diffWidth = width * oldScale - newWidth;
const diffHeight = height * oldScale - newHeight;
// 鼠标在图片上坐标比例, offsetX 是取原始大小的值, 所用要除 widht
const xRatio = offsetX / width;
const yRatio = offsetY / height;
// 需要再次移动的距离 x = (新的宽度 - 旧的宽度) * 鼠标在旧的宽度的比例
return { x: diffWidth * xRatio, y: diffHeight * yRatio };
};
return {
wheelZoom,
reset,
};
};
const main = document.getElementById("main");
const { wheelZoom } = useZoom(main as any, (transform, scale) => {
// translate 和 scale 的顺序影响最终效果
main!.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${scale})`;
});
main!.addEventListener("wheel", wheelZoom);
});
四、自定义拖拽指令
draggable.ts文件
ts
// 引入vue中定义的指令对应的类型定义
import { type Directive } from 'vue'
export const Draggable: Directive = {
// mounted是指令的一个生命周期
mounted(el, b) {
const oDiv = el // 当前元素
oDiv.style.position = 'absolute'
// let self = this // 上下文
// 禁止选择网页上的文字
document.onselectstart = function () {
return false
}
oDiv.onmousedown = function (e:any) {
const disW = oDiv.offsetWidth
const disH = oDiv.offsetHeight
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - oDiv.offsetLeft
const disY = e.clientY - oDiv.offsetTop
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离
const l = e.clientX - disX
const t = e.clientY - disY
// 移动当前元素
oDiv.style.left = l + 'px'
oDiv.style.top = t + 'px'
}
document.onmouseup = function () {
document.onmousemove = null
document.onmouseup = null
}
// return false不加的话可能导致黏连,就是拖到一个地方时div粘在鼠标上不下来,相当于onmouseup失效
return false
}
}
}
和draggable.ts同文件夹下建一个index.ts
ts
// 拖拽指令
export * from './draggable'
在mian.ts中注册全局自定义指令
ts
import * as directives from './directives'
// 注册全局自定义指令
Object.keys(directives).forEach(key => { //Object.keys() 返回一个数组,值是所有可遍历属性的key名
app.directive(key, (directives as { [key: string ]:Directive })[key]) //key是自定义指令名字;后面应该是自定义指令的值,值类型是string
})
在MapEcharts.vue中为echarts容器添加拖拽指令
html
<div id="main" v-draggable></div>
结语
在BI大屏中是一个比较常用的功能所以就想记录一下 这个功能的技术是难点,主要是一个思路的转换。 需要源代码的话看需求的人多不多,多的话就有时间整理出来