Vue3.2+Echarts叠加图层并且实现对地图的放大缩小功能

前言

做大屏的时候常有一个地图的功能需求,地图经常需要能做到对地图进行缩放拖拽等功能

但是地图要做好看,就必须要对地图进行叠加图层,叠加之后对地图进行缩放等功能就会出现只能缩放一个图层的问题

效果图展示

目标效果

  • ✅效果一:地图的叠加效果
  • ✅效果二:地图叠加图层情况下进行方法缩小,图层不乱
  • ✅效果三:地图拖拽效果
  • ✅效果四:地图根据经纬度标点位置

效果实现思路

关闭地图原本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大屏中是一个比较常用的功能所以就想记录一下 这个功能的技术是难点,主要是一个思路的转换。 需要源代码的话看需求的人多不多,多的话就有时间整理出来

相关推荐
前端之虎陈随易1 天前
2年没用Nodejs了,Bun很香
linux·前端·javascript·vue.js·typescript
好运的阿财1 天前
OpenClaw工具拆解之host_workspace_write+host_workspace_edit
前端·javascript·人工智能·机器学习·ai编程·openclaw·openclaw工具
XiYang-DING1 天前
JavaScript
开发语言·javascript·ecmascript
空中海1 天前
02 React Native状态、导航、数据流与设备能力
javascript·react native·react.js
空中海1 天前
02 状态、Hooks、副作用与数据流
开发语言·javascript·ecmascript
空中海1 天前
04 React Native工程化、质量、发布与生态选型
javascript·react native·react.js
杨超凡1 天前
豆包收费了?我特么自己用“意念”搓了一个!
javascript
threelab1 天前
Three.js 咖啡杯烟雾效果 | 三维可视化 / AI 提示词
开发语言·javascript·人工智能
Heo1 天前
14_React 中的更新队列 updateQueue
前端·javascript·面试