原生 js 实现类 3d 地图大屏展示自动高亮轮播、显示悬浮提示 tootip 的方案:svg + popper.js 定位引擎

简介: 原生 js 实现类 3d 地图大屏展示自动高亮轮播、显示悬浮提示 tootip 的方案:svg + popper.js 定位引擎

要实现的效果

如下图,3d 地图高亮自动轮播,展示白云区各个镇街的人口数局。

d7ce7942519f490aace008266fcbd898.gif



原由


为什么想到这个方案,是因为我在用 echarts-gl 实现 3d 地图效果的过程中,我发现通过 dispatchAction 触发不了 3d 的高亮。不知道大家有没有遇到,所以我实现不出自动轮播的 3d 效果,因为显示悬浮提示 tooltip 不出来,必须要 hover 的时候才行。(不知道是不是我的配置问题,如果大家有好的 3d 地图自动轮播方案,欢迎在下面评论分享。)


于是,我在想能不能用 svg 直接渲染成 dom,然后在定时的修改对应 dom 的样式,并且显示出对应标签的 tooltip 提示。我觉得可以一试,于是我找了一些相关资料,简单的实现了一下。



代码实现


1、修改地图的视觉提供地图的 svg 图

由于 svg 的字符过多,博客放不下,这里就不展示 svg 的全部代码了。


主要的就是为了方便激活地块,快速的找到 dom 元素,我们需要给地块加一些 id,还有标签定位 tooltip,方便获取跟标记。

<g id="plot" name="地块" transform="translate(0.000000, -0.000000)">
  <g id="plot_yuncheng">
    <use fill="#eee" fill-rule="evenodd" xlink:href="#path-47"></use>
    <use fill="black" fill-opacity="1" filter="url(#filter-48)" xlink:href="#path-47"></use>
    <use stroke="#aaa" stroke-width="2" xlink:href="#path-47"></use>
  </g>
...
</g>
<g id="label" name="标签" transform="translate(80.000000, 128.000000)">
  <g id="label_renhe" transform="translate(176.000000, 81.000000)">
    <g id="编组-77备份-3">
      <path d="M71.0581411,-0.5 L71.0581411,24.3372098 L-0.5,24.3372098 L-0.5,-0.5 L71.0581411,-0.5 Z"
        id="矩形备份-37" stroke="#fff" fill="orange"></path>
      <path
        d="M70.5581411,19.8643415 L70.5581411,23.8372098 L66.8722681,23.8372098 L70.5581411,19.8643415 Z M3.68587304,0 L0,3.97286831 L0,0 L3.68587304,0 Z"
        id="形状结合" fill="#fff"></path>
    </g>
    <text id="人和镇" font-family="PingFangSC-Medium, PingFang SC" font-size="14" font-weight="400" fill="#FFFFFF">
      <tspan x="14.1116282" y="16.9186049">人和镇</tspan>
    </text>
  </g>
  ...
</g>


2、引入 popper.js 定位引擎

官网:TOOLTIP & POPOVER POSITIONING ENGINE


8ecfe97f0c7a4747bc2e094423603b10.png



第一种使用方式:

npm i @popperjs/core -s


import { createPopper } from '@popperjs/core';
const popcorn = document.querySelector('#popcorn');
const tooltip = document.querySelector('#tooltip');
createPopper(popcorn, tooltip, {
  placement: 'top',
});


第二种使用方式:

在 html 中引入 popper.js 定位引擎:

<script src="https://unpkg.com/@popperjs/core@2"></script>
<!DOCTYPE html>
<html>
  <head>
    <title>Popper Tutorial</title>
  </head>
  <body>
    <button id="button" aria-describedby="tooltip">My button</button>
    <div id="tooltip" role="tooltip">My tooltip</div>
    <script src="https://unpkg.com/@popperjs/core@2"></script>
    <script>
      const button = document.querySelector('#button');
      const tooltip = document.querySelector('#tooltip');
      const popperInstance = Popper.createPopper(button, tooltip);
    </script>
  </body>
</html>


3、激活地块

需要给 use 元素设置激活的 style 属性

let activedStyle = "fill:blue"; // 地块激活样式
// 激活地块
function addPlotActive(index) {
  baiyunSvgDom.getElementById(`plot_${plotArrs[index]}`).getElementsByTagName('use')[0].setAttribute("style", activedStyle);
}


4、tooltip 定位

我们需要找到当前轮播的标签元素,以及提示框的元素,然后通过 Popper.createPopper 设置就行。

// 设置标签提示定位
function setCreatePopper() {
  Popper.createPopper(baiyunSvgDom.getElementById(`label_${plotArrs[index]}`), mapTooltipsDom, {
    placement: 'top-start',
    positionFixed: true
  });
}


5、自动轮播跟赋值

自动轮播的话主要需要注意第一次跟最后一次就行,另外赋值的时候,innerHTML 不要写错了。具体代码的话看逻辑代码即可。



逻辑代码

这里的代码缺失了 svg 里的内容。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SVG地图轮播</title>
  <style>
    .svg-geo-map-tooltips {
      min-width: 160px;
      background-color: rgba(0,0,0,.8);
      border-radius: 8px;
      text-align: center;
      padding: 18px 8px;
      transition: .4s;
      left: 20px !important;
      bottom: 5px !important;
    }
    .svg-geo-map-tooltips .name {
      font-size: 16px;
      font-weight: 400;
      color: #ffffff;
      height: 22px;
      line-height: 22px;
      margin-bottom: 8px;
    }
    .svg-geo-map-tooltips .value {
      color: #ffffff;
    }
    .svg-geo-map-tooltips .value .num {
      font-size: 24px;
      font-weight: bold;
      height: 32px;
      line-height: 32px;
    }
    .svg-geo-map-tooltips .value .unit {
      font-size: 14px;
    }
  </style>
  <script src="https://unpkg.com/@popperjs/core@2"></script>
</head>
<body>
  <div class='svg-geo-map-chart'>
    <svg id="baiyunSvg" width="804px" height="597px" viewBox="0 0 804 597" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
    <!-- 提示 -->
    <div class="svg-geo-map-tooltips">
      <div class="name"></div>
      <div class="value">
        <span class="num"></span>
        <span class="unit">人</span>
      </div>
    </div>
  </div>
  <script>
    let curPlotName = ""; // 当前激活的地块名
    let curPlotValue = 0; // 当前激活的地块服务量数
    let interval = 1000; // 时间间隔毫秒数
    let index = 0; // 播放所在下标
    let timer = null;
    let activedStyle = "fill:blue"; // 地块激活样式
    let baiyunSvgDom = null; // svg 的 dom 元素
    let mapTooltipsDom = null; // 提示的 dom 元素
    let baiyunPlotDataList = [{
      id: "jianggao",
      name: "江高镇",
      value: 883945
    },{
      id: "renhe",
      name: "人和镇",
      value: 4567992
    },{
      id: "taihe",
      name: "太和镇",
      value: 4567323
    },{
      id: "zhongluotan",
      name: "钟落潭镇",
      value: 497863
    },{
      id: "longgui",
      name: "龙归街",
      value: 3486257
    },{
      id: "dayuan",
      name: "大源街",
      value: 897435
    },{
      id: "sanyuanli",
      name: "三元里街",
      value: 46809544
    },{
      id: "songzhou",
      name: "松洲街",
      value: 123403
    },{
      id: "jingtai",
      name: "景泰街",
      value: 342256677
    },{
      id: "tongde",
      name: "同德街",
      value: 234677
    },{
      id: "huangshi",
      name: "黄石街",
      value: 976542
    },{
      id: "tangjing",
      name: "棠景街",
      value: 33456
    },{
      id: "xinshi",
      name: "新市街",
      value: 3455602
    },{
      id: "tonghe",
      name: "同和街",
      value: 487654
    },{
      id: "jingxi",
      name: "京溪街",
      value: 3876735
    },{
      id: "yongping",
      name: "永平街",
      value: 6677544
    },{
      id: "jiahe",
      name: "嘉禾街",
      value: 34526784
    },{
      id: "junhe",
      name: "均禾街",
      value: 8756534
    },{
      id: "shijing",
      name: "石井街",
      value: 220232
    },{
      id: "jinsha",
      name: "金沙街",
      value: 3352256
    },{
      id: "yuncheng",
      name: "云城街",
      value: 335677
    },{
      id: "helong",
      name: "鹤龙街",
      value: 334225
    },{
      id: "baiyunhu",
      name: "白云湖街",
      value: 34556
    },{
      id: "shimen",
      name: "石门街",
      value: 1354667
    }]; // 默认地块数据
    let plotArrs = baiyunPlotDataList.map(el => el.id); // 地块的id数据
    // 数字加千位分隔符
    function numToThsSprtr (num) {
      let res = num.toString().replace(/\d+/, function (n) { // 先提取整数部分
        return n.replace(/(\d)(?=(\d{3})+$)/g, function ($1) {
          return $1 + ',';
        });
      })
      return res;
    }
    // 初始化
    function initRender() {
      // 获取 dom 元素
      baiyunSvgDom = document.getElementById('baiyunSvg');
      mapTooltipsDom = document.querySelector('.svg-geo-map-tooltips');
      // 地块的id数据
      plotArrs = baiyunPlotDataList.map(el => el.id);
      // 初始化激活颜色
      this.addPlotActive(index);
      // 赋值
      setPlotValue();
      console.log(index, plotArrs[index], curPlotName, curPlotValue);
      // 初始化定位
      this.setCreatePopper();
    }
    // 设置轮播
    function setIntervalSvg() {
      timer = setInterval(() => {
        // 先清除上一次的激活效果
        if(index !== -1) {
          removePlotActive(index);
        }
        // 索引自增
        index++;
        // 地块激活
        this.addPlotActive(index);
        // 赋值
        setPlotValue();
        console.log(index, plotArrs[index], curPlotName, curPlotValue);
        // 进行提示定位
        this.setCreatePopper();
        // 处理最后一个地块激活问题
        if(index === plotArrs.length - 1) {
          let tempTimer = setTimeout(() => {
            // 移除地块最后一个的激活
            removePlotActive(plotArrs.length - 1);
            clearTimeout(tempTimer);
          }, interval);
          index = -1;
        }
      }, interval);
    }
    // 激活地块
    function addPlotActive(index) {
      baiyunSvgDom.getElementById(`plot_${plotArrs[index]}`).getElementsByTagName('use')[0].setAttribute("style", activedStyle);
    }
    // 赋值
    function setPlotValue() {
      curPlotName = baiyunPlotDataList[index].name;
      curPlotValue = numToThsSprtr(baiyunPlotDataList[index].value);
      mapTooltipsDom.querySelector('.name').innerHTML = `${curPlotName}常住人口`;
      mapTooltipsDom.querySelector('.num').innerHTML = curPlotValue;
    }
    // 移除地块激活
    function removePlotActive(index) {
      baiyunSvgDom.getElementById(`plot_${plotArrs[index]}`).getElementsByTagName('use')[0].removeAttribute("style", activedStyle);
    }
    // 设置标签提示定位
    function setCreatePopper() {
      Popper.createPopper(baiyunSvgDom.getElementById(`label_${plotArrs[index]}`), mapTooltipsDom, {
        placement: 'top-start',
        positionFixed: true
      });
    }
    // 初始化
    initRender();
    // 设置轮播
    setIntervalSvg();
  </script>
</body>
</html>



完整代码

里面包含视频,图片,全部代码,由于展示不了 svg 的全部代码。下面我整理了资源,有需要的可以下载。

3af46ae4425b4e82bb70e0815763dddd.png

完整资源下载

21c1057a7cf84f32a1cfe6f07ba3fb1a.png



参考资料


目录
相关文章
|
17天前
|
JSON JavaScript 前端开发
JavaScript原生代码处理JSON的一些高频次方法合集
JavaScript原生代码处理JSON的一些高频次方法合集
|
1月前
|
JavaScript
js开发:请解释什么是ES6的类(class),并说明它与传统构造函数的区别。
ES6的类提供了一种更简洁的面向对象编程方式,对比传统的构造函数,具有更好的可读性和可维护性。类使用`class`定义,`constructor`定义构造方法,`extends`实现继承,并可直接定义静态方法。示例展示了如何创建`Person`类、`Student`子类以及它们的方法调用。
22 2
|
1月前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承利用原型链查找属性,节省内存但不支持私有成员。类继承通过ES6的class和extends实现,支持私有成员但占用更多内存。两者各有优势,适用于不同场景。
19 0
|
1月前
uni-app 65egg.js聊天类chat.js封装(二)
uni-app 65egg.js聊天类chat.js封装(二)
25 1
|
1月前
|
XML 编解码 前端开发
编程笔记 html5&css&js 033 HTML SVG
编程笔记 html5&css&js 033 HTML SVG
|
2月前
|
JavaScript
原生js实现复选框(全选/全不选/反选)效果【含完整代码】
原生js实现复选框(全选/全不选/反选)效果【含完整代码】
58 1
|
17天前
|
JavaScript 算法
原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞检查,通过计算接触面积(重叠覆盖面积)大小来判断接触对象DOM
原生JS完成“一对一、一对多”矩形DIV碰撞检测、碰撞检查,通过计算接触面积(重叠覆盖面积)大小来判断接触对象DOM
|
3月前
|
前端开发 JavaScript 索引
|
3天前
|
JavaScript 前端开发 UED
深入解析JavaScript原生操作DOM技术
【4月更文挑战第22天】本文深入探讨JavaScript原生DOM操作技术,包括使用`getElement*`方法和CSS选择器获取元素,借助`createElement`与`appendChild`动态创建及插入元素,修改元素内容、属性和样式,以及删除元素。通过掌握这些技术,开发者能实现页面动态交互,但应注意避免过度操作DOM以优化性能和用户体验。
|
17天前
|
JavaScript
【归总】原生js操作浏览器hash、url参数参数获取/修改方法合集
【归总】原生js操作浏览器hash、url参数参数获取/修改方法合集