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

相关推荐
还是大剑师兰特34 分钟前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts
一只小白菜~40 分钟前
web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
前端·javascript·pdf·windowopen预览pdf
方才coding1 小时前
1小时构建Vue3知识体系之vue的生命周期函数
前端·javascript·vue.js
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
阿征学IT1 小时前
vue过滤器初步使用
前端·javascript·vue.js
王哲晓1 小时前
第四十五章 Vue之Vuex模块化创建(module)
前端·javascript·vue.js
发现你走远了1 小时前
『VUE』25. 组件事件与v-model(详细图文注释)
前端·javascript·vue.js
吖秧吖1 小时前
three.js 杂记
开发语言·前端·javascript
前端小超超1 小时前
vue3 ts项目结合vant4 复选框+气泡弹框实现一个类似Select样式的下拉选择功能
前端·javascript·vue.js
大叔是90后大叔1 小时前
vue3中查找字典列表中某个元素的值
前端·javascript·vue.js