原生 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



参考资料


目录
相关文章
|
22天前
|
JavaScript 前端开发 测试技术
在 golang 中执行 javascript 代码的方案详解
本文介绍了在 Golang 中执行 JavaScript 代码的四种方法:使用 `otto` 和 `goja` 嵌入式 JavaScript 引擎、通过 `os/exec` 调用 Node.js 外部进程以及使用 WebView 嵌入浏览器。每种方法都有其适用场景,如嵌入简单脚本、运行复杂 Node.js 脚本或在桌面应用中显示 Web 内容。
55 15
在 golang 中执行 javascript 代码的方案详解
|
14天前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
17天前
|
Web App开发 移动开发 HTML5
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码。画面中心是悬浮于空的梅花鹿,其四周由白色线段组成了一个6边形将中心的梅花鹿包裹其中。四周漂浮的白雪随着多边形的转动而同步旋转。建议使用支持HTML5与css3效果较好的火狐(Firefox)或谷歌(Chrome)等浏览器预览本源码。
59 2
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
44 0
|
3月前
|
自然语言处理 JavaScript 前端开发
一文梳理JavaScript中常见的七大继承方案
该文章系统地概述了JavaScript中七种常见的继承模式,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承、寄生组合继承等,并探讨了每种模式的实现方式及其优缺点。
一文梳理JavaScript中常见的七大继承方案
|
2月前
|
JavaScript
js文字如何轮播?
js文字如何轮播?
15 1
|
3月前
|
移动开发 前端开发 JavaScript
原生JavaScript+canvas实现五子棋游戏_值得一看
本文介绍了如何使用原生JavaScript和HTML5的Canvas API实现五子棋游戏,包括棋盘的绘制、棋子的生成和落子、以及判断胜负的逻辑,提供了详细的代码和注释。
51 0
原生JavaScript+canvas实现五子棋游戏_值得一看
|
2月前
|
JavaScript 前端开发
原生js常见报错及其处理方案
原生js常见报错及其处理方案
43 0
|
4月前
|
JavaScript
js文字如何轮播?
js文字如何轮播?
31 2
|
4月前
|
编解码 缓存 算法
Three.js如何降低3D模型的大小以便更快加载
为加快600MB的3D模型在Three.js中的加载速度,可采用多种压缩方法:1) 减少顶点数,使用简化工具或LOD技术;2) 压缩纹理,降低分辨率或转为KTX2等格式;3) 采用高效文件格式如glTF 2.0及draco压缩;4) 合并材质减少数量;5) 利用Three.js内置优化如BufferGeometry;6) 按需分批加载模型;7) Web Workers后台处理;8) 多模型合并减少绘制;9) 使用Texture Atlas及专业优化工具。示例代码展示了使用GLTFLoader加载优化后的模型。
536 12