要实现的效果
如下图,3d 地图高亮自动轮播,展示白云区各个镇街的人口数局。
原由
为什么想到这个方案,是因为我在用 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
第一种使用方式:
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 的全部代码。下面我整理了资源,有需要的可以下载。