本文还有配套的精品资源点击获取简介直接打开index.html就能看到旋转的3D地球鼠标悬停到任意区域立刻弹出样式完全可控的tooltip。这个包把ECharts 3D模式下原本不显示的tooltip问题彻底解决通过map.js配置交互逻辑common.css统一管理背景色、边框圆角、字体大小、阴影深度、箭头方向等细节所有样式规则都支持按需修改。js目录封装了地理数据加载、视角控制和事件绑定css目录集中存放视觉规则结构清晰没有外部依赖。适配桌面和高分屏tooltip定位精准不会被3D容器裁切内容可嵌入HTML片段支持动态数据更新。本地双击index.html即可运行无需构建工具、服务器或Node环境适合快速集成到现有项目中做地理热点展示、全球业务分布示意或实时数据映射。1. 项目概述为什么一个“能显示的tooltip”值得单独打包你有没有试过在 ECharts 里打开globe3D地球组件兴冲冲地配好tooltip: { show: true }结果鼠标划过去——什么都没发生不是卡了不是没加载是ECharts 3D 模式下 tooltip 默认根本不会渲染到 DOM 中。它压根不走常规的div.tooltip插入逻辑而是依赖 WebGL 渲染层内部的简易文本绘制仅支持纯文本、无样式、无交互、定位漂移、高分屏糊成一片。这问题从 ECharts 4.9 到 5.4 都没变官方文档里轻描淡写一句“3D 视图 tooltip 支持有限”背后是无数前端同学在控制台反复刷新、检查zrender层级、怀疑自己配置漏了trigger或formatter的深夜。这个包解决的不是“怎么让 tooltip 更好看”的锦上添花问题而是“让 tooltip 先活下来”的生存问题。它用一套极简但精准的 JS-CSS 协同机制把原本被 WebGL “封印”在渲染管线里的提示信息完整、实时、像素级对齐地“拽”回真实 DOM 层——这意味着你能用 CSS 写backdrop-filter: blur(10px)做毛玻璃效果能用media (prefers-reduced-motion: reduce)降级动画能嵌入img加载国家旗帜 SVG甚至能塞一个带v-model的 Vue 组件占位符后续扩展用。它不碰 ECharts 内核不 hack zrender不引入任何第三方 tooltip 库比如 tippy.js 或 popper.js所有逻辑都建立在 ECharts 官方暴露的dispatchAction和on(mousemove)事件之上因此兼容性极强ECharts 5.0 全系支持Chrome/Firefox/Edge 最新 3 个大版本实测稳定Safari 16.4 在 macOS Ventura 上也跑得顺滑。我把它做成“开箱即用”是因为真正在业务中落地时没人想花半天配 webpack alias、写 loader 解析.geo.json、调globe.viewControl的 pitch roll 值来对齐 tooltip 坐标。你双击index.html看到地球自转、鼠标悬停出带阴影圆角的卡片、点开common.css改两行--tooltip-bg就换主题色——这就是它该有的样子。它面向三类人需要快速验证全球数据分布的产品经理、要嵌入现有后台系统的技术支持同事、以及像我这样常被临时拉去改“那个地球页面”的前端救火队员。没有构建步骤没有环境依赖连 Node.js 都不需要装——因为真正的部署场景里你最后扔给运维的往往就是一个 zip 包解压后丢进 Nginx 的html/目录。2. 整体设计思路JS 负责“算位置”CSS 负责“长样子”这个方案的核心思想就八个字分离关注各司其职。JS 不画 UICSS 不算坐标。很多人一上来就想用document.createElement(div)手动创建 tooltip 并appendChild结果发现地球旋转时 tooltip 死死钉在屏幕左上角或者用echartsInstance.setOption()动态改tooltip.textStyle却发现字体颜色能变阴影和圆角永远灰扑扑——这是因为 ECharts 3D 的 tooltip 是 canvas 绘制的位图不是可编辑的 DOM 元素。我们彻底绕开这个坑让 JS 只做一件事——实时计算鼠标在 3D 球面上对应经纬度的屏幕像素坐标并把数据推给一个固定 DOM 节点其余所有视觉表现全部交给 CSS 控制。整个流程像一条流水线地理坐标捕获监听chart.on(mousemove)拿到params对象里的name区域名、value数值、dataIndex索引三维转二维调用chart.convertFromPixel({ seriesIndex: 0 }, [x, y])获取当前鼠标在系列坐标系中的归一化值再通过globe实例的getDistanceFromCenter和getScreenCoordByGeo需 patch反向推算球面经纬度像素定位输出将计算出的{ left: px, top: px }写入一个全局div idcustom-tooltip的style.left/topCSS 主导渲染#custom-tooltip本身无内容靠::before伪元素承载文字::after画箭头所有样式包括transform: translate(-50%, -100%)实现指针尖端对齐均由common.css定义。提示这里的关键突破点在于getScreenCoordByGeo方法。ECharts 原生 globe 并未暴露此 API我们在map.js开头注入了一段轻量 patchjs // 修补 globe 实例添加地理坐标转屏幕坐标的工具方法 echarts.registerAction({ type: globeToScreen, event: globeToScreen }, () {}); const originalSetOption echartsInstance.setOption; echartsInstance.setOption function(...args) { originalSetOption.apply(this, args); if (this.getModel().getComponent(globe)) { this.globeToScreen function(longitude, latitude) { const coord this.getModel().getComponent(globe).coordinateSystem.getScreenCoord(longitude, latitude); return { x: coord[0], y: coord[1] }; }; } };这段代码只执行一次在图表初始化后动态挂载方法不污染原型链不影响其他图表实例。这种设计带来三个直接好处第一定位绝对精准——箭头尖端始终落在鼠标正下方 8px 处不会因地球旋转或缩放偏移第二样式完全自由——你可以把#custom-tooltip::before改成content: attr(data-html);然后在 JS 里el.setAttribute(data-html, b中国/bbr/活跃用户: span stylecolor:#ff6b6b24.7万/span);实现 HTML 片段注入第三响应式天然友好——common.css里用clamp(1rem, 2.5vw, 1.25rem)控制字体大小用max-width: min(300px, 80vw)限制卡片宽度高分屏下自动放大小屏手机上收缩为紧凑卡片。3. 核心细节解析从 common.css 到 map.js 的每一处设计深意3.1 common.css不只是“重置”而是 tooltip 的“宪法”common.css表面看只是几行样式实则定义了整个 tooltip 系统的行为边界。它不是简单覆盖浏览器默认样式而是构建了一套可继承、可覆盖、可主题化的 CSS 自定义属性体系。打开文件你会看到这样的结构:root { --tooltip-bg: hsl(210, 15%, 12%); --tooltip-border: hsl(210, 10%, 30%); --tooltip-text: hsl(0, 0%, 95%); --tooltip-shadow: 0 8px 24px hsla(210, 20%, 10%, 0.4); --tooltip-radius: 12px; --tooltip-arrow-size: 8px; --tooltip-padding: 16px; --tooltip-font-size: clamp(0.875rem, 2.5vw, 1.125rem); }这些变量不是随便起的。--tooltip-bg用 HSL 而非 HEX是为了方便后续通过 JS 动态切换主题比如夜间模式document.documentElement.style.setProperty(--tooltip-bg, hsl(240, 10%, 8%));--tooltip-shadow的hsla()第四个参数控制透明度避免深色背景上阴影过重--tooltip-radius设为12px是经过实测的黄金值——小于 8px 显得尖锐不友好大于 16px 在小尺寸 tooltip 上弧度过大易误触。最关键的是#custom-tooltip的定位策略#custom-tooltip { position: fixed; z-index: 10000; /* 必须高于 ECharts canvas */ pointer-events: none; /* 确保鼠标穿透不阻断地球交互 */ transform: translate(-50%, -100%); /* 关键让箭头尖端对齐鼠标 */ transition: opacity 0.2s ease, transform 0.2s ease; } #custom-tooltip::before, #custom-tooltip::after { content: ; position: absolute; pointer-events: none; } #custom-tooltip::before { background: var(--tooltip-bg); border: 1px solid var(--tooltip-border); border-radius: var(--tooltip-radius); box-shadow: var(--tooltip-shadow); padding: var(--tooltip-padding); color: var(--tooltip-text); font-size: var(--tooltip-font-size); line-height: 1.5; white-space: nowrap; min-width: 120px; } #custom-tooltip::after { /* 箭头三角形用 border 技巧实现 */ width: 0; height: 0; border-left: var(--tooltip-arrow-size) solid transparent; border-right: var(--tooltip-arrow-size) solid transparent; border-top: var(--tooltip-arrow-size) solid var(--tooltip-bg); bottom: calc(var(--tooltip-arrow-size) * -1); left: 50%; transform: translateX(-50%); }这里有两个极易被忽略的细节第一pointer-events: none必须同时加在#custom-tooltip和它的伪元素上否则当 tooltip 覆盖地球区域时鼠标会“卡住”无法触发mousemove事件导致 tooltip 消失第二transform: translate(-50%, -100%)是实现“箭头尖端精准锚定鼠标”的核心——它让整个 tooltip 的中心点X轴和顶部边缘Y轴对齐鼠标坐标箭头::after再用bottom: -8px向下延伸自然形成指向效果。如果你删掉这行tooltip 会整体下移箭头悬空。3.2 map.js地理数据加载与交互逻辑的“中枢神经”map.js是整个项目的逻辑心脏但它刻意保持极度精简——只有 218 行含注释没有一行冗余代码。它的结构分为四块模块导入与基础配置第 1–32 行声明echarts全局依赖定义GEOJSON_URL指向内置的world.json设置DEFAULT_VIEW初始视角经度 0°、纬度 30°、缩放 1.2地理数据预处理第 34–87 行读取world.json后遍历每个feature.properties.name标准化国名如United States→美国并为每个区域生成随机模拟数据value: Math.floor(Math.random() * 100000)这部分在真实项目中会被替换成你的 API 请求图表实例创建与 globe 配置第 89–152 行这是最易出错的部分。关键配置项包括baseTexture: /assets/earth-day.jpg指定地球贴图路径必须是 2048×1024 或 4096×2048 的 equirectangular 投影图heightTexture: /assets/earth-height.jpg高度图用于地形起伏若省略则为光滑球体environment: /assets/earth-env.jpg环境贴图实现反射效果提升真实感viewControl.autoRotate: true开启自动旋转viewControl.target: [0, 0, 0]确保旋转中心是球心而非画布中心交互事件绑定与 tooltip 同步第 154–218 行核心逻辑在此。监听mousemove时先判断params.componentType series params.seriesType scatter确保只响应散点系列再调用chart.globeToScreen(params.value[0], params.value[1])获取屏幕坐标最后更新#custom-tooltip的style.left/top和dataset属性。注意params.value[0], params.value[1]是经纬度不是像素坐标。很多同学直接拿params.event.offsetX/Y结果 tooltip 在地球旋转时疯狂抖动——因为 offset 是相对于 canvas 画布的而 canvas 本身在随 globe 缩放平移。必须用globeToScreen做地理坐标到屏幕坐标的转换这才是唯一可靠的方案。3.3 index.html零依赖启动的“魔法门”index.html看似普通实则暗藏玄机。它没有引用 CDN 上的 ECharts避免网络波动导致白屏而是把echarts.min.js和echarts-gl.min.js打包进了js/目录并做了版本锁定!-- js/echarts.min.js?v5.4.3 -- !-- js/echarts-gl.min.js?v2.0.9 --?v后缀强制浏览器缓存新版本避免旧版 JS 导致兼容问题。更关键的是body底部的初始化脚本script // 等待 DOM 加载完成再初始化图表 document.addEventListener(DOMContentLoaded, () { // 检查是否支持 WebGL const canvas document.createElement(canvas); const gl canvas.getContext(webgl) || canvas.getContext(experimental-webgl); if (!gl) { document.body.innerHTML div stylepadding:40px;text-align:center;color:#f44336;font-size:1.2rem;您的浏览器不支持 WebGL请升级 Chrome/Firefox/Edge 或使用 Safari 16/div; return; } // 动态加载地图数据并初始化 loadMapData().then(initChart); }); /script这段代码做了三件事第一主动检测 WebGL 支持失败时给出明确错误提示而不是让地球黑屏让用户猜第二用DOMContentLoaded替代window.onload确保 DOM 就绪即启动无需等待图片等资源第三loadMapData()返回 Promise支持后续接入真实 API比如fetch(/api/countries)而不仅是读取本地 JSON。4. 实操过程详解从双击运行到定制开发的每一步4.1 本地零配置运行三步确认是否成功别急着改代码先验证环境是否正常。按以下顺序操作解压资源包进入目录双击index.html注意不是用 VS Code Live Server也不是npx http-server就是原生双击浏览器打开后观察地址栏——应该是file:///xxx/xxx/index.html且控制台无红色报错等待约 2 秒地球贴图加载你应该看到一个缓慢自转的蓝色地球鼠标移到任意大陆上1 秒内弹出带阴影圆角的灰色卡片内容类似日本\n数值: 84231。如果卡在第一步检查文件路径是否含中文或空格Windows 下某些浏览器会拒绝加载file://协议下的中文路径重命名为纯英文目录再试。如果卡在第二步打开开发者工具F12切到 Console 标签页查找WebGL not supported错误。此时请关闭浏览器硬件加速设置 → 系统 → 关闭“使用硬件加速模式”或换 Chrome 115 / Firefox 116。如果卡在第三步鼠标悬停无反应但地球能转——说明map.js里的事件监听没生效。检查index.html中script src./js/map.js/script路径是否正确注意是./js/不是/js/或js/并在map.js第 154 行chart.on(mousemove, ...)前加一行console.log(chart initialized:, chart);确认chart实例已创建。实操心得我曾在线上环境遇到 tooltip 闪烁问题最终发现是common.css中transition: opacity 0.2s和transform 0.2s同时触发导致重绘冲突。解决方案是把opacity动画单独抽离transform保持即时生效transition: opacity 0.2s ease;并在 JS 更新坐标时手动加el.style.transform translate(-50%, -100%);彻底规避浏览器合成层争抢。4.2 修改 tooltip 样式修改 common.css 的五种典型场景common.css的设计目标是“改一行立竿见影”。以下是业务中最常遇到的五种定制需求及对应修改场景一适配公司品牌色把--tooltip-bg从hsl(210, 15%, 12%)改为你们主色调比如科技蓝hsl(200, 80%, 50%)同时微调--tooltip-text为白色hsl(0, 0%, 100%)--tooltip-border用稍浅的hsl(200, 70%, 60%)增强层次。场景二增加数据图标在#custom-tooltip::before的content后追加伪元素#custom-tooltip::before { /* 原有样式 */ content: attr(data-name) \A attr(data-value); white-space: pre; } #custom-tooltip::after { /* 原有箭头 */ } /* 新增图标 */ #custom-tooltip::first-line { display: inline-block; width: 20px; height: 20px; margin-right: 8px; background: url(/assets/icon-user.svg) no-repeat center; background-size: contain; }然后在 JS 里el.setAttribute(data-name, 德国); el.setAttribute(data-value, 活跃用户: 12.3万);图标自动出现在首行左侧。场景三高分屏优化MacBook Pro 16” 用户反馈 tooltip 文字太小。在common.css底部追加媒体查询media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { :root { --tooltip-font-size: clamp(1rem, 3vw, 1.375rem); --tooltip-padding: 20px; } }场景四禁用动画追求极致性能老旧笔记本上 tooltip 拖尾。注释掉#custom-tooltip的transition行并在 JS 更新坐标时直接设el.style.opacity 1;移除所有setTimeout(() el.style.opacity 0, 100)类逻辑。场景五多语言支持在map.js的formatter函数里根据navigator.language切换文案const lang navigator.language || zh-CN; const texts { zh-CN: { title: 国家, value: 数值 }, en-US: { title: Country, value: Value } }; // 然后在 tooltip 更新时 el.setAttribute(data-title, texts[lang].title); el.setAttribute(data-value, texts[lang].value : params.value);4.3 接入真实数据替换模拟数据的三步法map.js里的随机数据只是占位符。接入真实数据只需三步第一步准备地理数据源确保你的数据是 GeoJSON 格式每个feature包含properties.name国家名和properties.codeISO 3166-1 alpha-2 代码如CN。若数据来自后端 API返回结构应类似{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 中国, code: CN, users: 842312 }, geometry: { type: Polygon, coordinates: [...] } } ] }第二步修改loadMapData()函数将原fetch(./assets/world.json)替换为你的 APIasync function loadMapData() { try { const res await fetch(/api/global-stats); const geojson await res.json(); // 关键把后端数值映射到 GeoJSON 的 properties 中 geojson.features.forEach(f { f.properties.value f.properties.users || 0; f.properties.tooltip ${f.properties.name}br/用户数: ${f.properties.users.toLocaleString()}; }); return geojson; } catch (err) { console.error(Failed to load real data:, err); return fallbackWorldData(); // 降级到本地 world.json } }第三步更新 tooltip 渲染逻辑在chart.on(mousemove)回调中不再用params.value而是从params.data里取chart.on(mousemove, (params) { if (params.componentType series params.seriesType scatter) { const data params.data; const tooltipEl document.getElementById(custom-tooltip); tooltipEl.innerHTML ; // 清空旧内容 tooltipEl.setAttribute(data-name, data.name || ); tooltipEl.setAttribute(data-value, data.tooltip || ); // 计算坐标... } });注意params.data是你在series.data中传入的对象务必确保它包含name和tooltip字段。不要依赖params.name因为 ECharts 有时会截断长名称。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题速查表症状、原因、解决方案症状可能原因解决方案地球显示为纯黑色无纹理baseTexture路径错误或图片格式不支持非 JPG/PNG检查assets/earth-day.jpg是否存在用在线工具转为 sRGB 色彩空间确认路径是相对index.html的如./assets/...tooltip 始终显示在左上角不动chart.globeToScreen()返回undefined或params里没拿到经纬度在mousemove回调开头console.log(params)确认params.value是[lon, lat]数组检查map.js是否执行了globeToScreenpatch悬停时 tooltip 闪烁或消失pointer-events: none缺失或z-index不够高确认#custom-tooltip和::before/::after都有pointer-events: none把z-index提到99999测试高分屏下 tooltip 文字模糊common.css未启用image-rendering: -webkit-optimize-contrast在#custom-tooltip::before添加image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges;地球旋转时 tooltip 位置偏移使用了event.offsetX/Y而非globeToScreen()彻底删除所有offsetX/Y相关代码只保留chart.globeToScreen(params.value[0], params.value[1])5.2 独家避坑技巧来自 17 个真实项目的血泪总结技巧一防“地球撕裂”——贴图 UV 坐标校准ECharts globe 对贴图的 UV 坐标要求严格必须是标准的 equirectangular 投影经度 -180°~180° 映射到 U 0~1纬度 -90°~90° 映射到 V 0~1。我曾用一张 NASA 的 Blue Marble 图结果赤道附近出现明显接缝。解决方案用 GIMP 打开图片Filters → Map → Make Seamless再导出为 JPG。或者直接用 Natural Earth 提供的 10m 静态贴图它们已做过专业校准。技巧二解决“tooltip 被裁切”——容器 overflow 隐藏当把地球嵌入position: relative的父容器时#custom-tooltip可能被overflow: hidden截断。不要改父容器样式可能影响其他布局而是在common.css里强制#custom-tooltip脱离文档流#custom-tooltip { position: fixed !important; /* 覆盖任何可能的 relative/absolute */ inset: auto; /* 重置 top/right/bottom/left */ }技巧三动态数据更新不刷新 tooltip调用chart.setOption(option, true)后tooltip 内容没变。这是因为setOption不触发mousemove事件。解决方案在更新数据后手动触发一次假事件// 更新 option 后 chart.setOption(newOption, true); // 强制刷新 tooltip const lastPos { x: 100, y: 100 }; // 记录上次鼠标位置 chart.dispatchAction({ type: highlight, from: api, seriesIndex: 0, dataIndex: 0 }); // 再模拟一次 mousemove chart.getZr().handler.dispatch(mousemove, { offsetX: lastPos.x, offsetY: lastPos.y });技巧四移动端适配“悬停失效”手机上没有 hovermousemove不触发。必须补充touchmove事件chart.getZr().on(touchmove, (e) { if (e.zrEvent.touches.length 0) { const touch e.zrEvent.touches[0]; // 转换为 canvas 坐标 const rect chart.getDom().getBoundingClientRect(); const x touch.clientX - rect.left; const y touch.clientY - rect.top; // 手动触发 tooltip 更新 updateTooltipAt(x, y); } });技巧五调试坐标偏移的终极方法当 tooltip 总是偏右 20px你改了十次left值还是不准——打开common.css临时给#custom-tooltip加#custom-tooltip { outline: 2px dashed red !important; background: rgba(255,0,0,0.1) !important; }红框会清晰显示 tooltip 实际占据区域配合transform: translate(-50%, -100%)的数学关系一眼看出是left值该加还是该减。6. 后续可扩展方向不止于“能显示”更要“好用”这个包的定位是“最小可行 tooltip 方案”但它留出了清晰的扩展接口。我在三个客户项目中基于它做了如下增强代码均可复用扩展一时间轴联动在index.html加入 ECharts 时间轴组件拖动时调用chart.dispatchAction({ type: downplay, seriesIndex: 0 })清除高亮再chart.dispatchAction({ type: highlight, seriesIndex: 0, dataIndex: i })激活对应区域tooltip 自动跟随更新。关键是把dataIndex存在params.data里避免重复计算。扩展二点击钻取详情页给#custom-tooltip绑定click事件需先移除pointer-events: none点击后window.open(/country/${params.data.code}, _blank)跳转到国家详情页。为防误触加 300ms 延迟判断是否为长按。扩展三WebGL 性能监控在map.js初始化后插入let lastFrameTime 0; function checkFPS() { const now performance.now(); const delta now - lastFrameTime; if (delta 0) { const fps Math.round(1000 / delta); if (fps 30) { document.getElementById(custom-tooltip).style.boxShadow 0 0 0 2px #ff9800; setTimeout(() { document.getElementById(custom-tooltip).style.boxShadow ; }, 2000); } } lastFrameTime now; requestAnimationFrame(checkFPS); } requestAnimationFrame(checkFPS);当帧率低于 30tooltip 边框变橙色预警运维同学一眼可知需优化贴图分辨率。我自己在最后一个项目中把 tooltip 扩展成了一个迷你数据面板左侧显示国家基本信息国旗 SVG GDP右侧是折线图用 Canvas 2D 绘制近 12 个月用户趋势所有数据通过params.data.history注入。它没用任何图表库就靠common.css的grid-template-columns: 1fr 1fr和几行ctx.lineTo()却比接入 ECharts 子图表更轻量、更可控。这个包的价值从来不在炫技而在于它把一个被框架“放弃”的功能用最朴素的 Web 基础能力——JS 的坐标计算 CSS 的样式控制——重新夺了回来。当你下次再看到某个“高级可视化库”宣传“完美支持 3D tooltip”不妨打开它的源码看看那几百行 patch 里是不是也藏着和common.css里一模一样的transform: translate(-50%, -100%)。本文还有配套的精品资源点击获取简介直接打开index.html就能看到旋转的3D地球鼠标悬停到任意区域立刻弹出样式完全可控的tooltip。这个包把ECharts 3D模式下原本不显示的tooltip问题彻底解决通过map.js配置交互逻辑common.css统一管理背景色、边框圆角、字体大小、阴影深度、箭头方向等细节所有样式规则都支持按需修改。js目录封装了地理数据加载、视角控制和事件绑定css目录集中存放视觉规则结构清晰没有外部依赖。适配桌面和高分屏tooltip定位精准不会被3D容器裁切内容可嵌入HTML片段支持动态数据更新。本地双击index.html即可运行无需构建工具、服务器或Node环境适合快速集成到现有项目中做地理热点展示、全球业务分布示意或实时数据映射。本文还有配套的精品资源点击获取
ECharts 3D地球页面:开箱即用的CSS可定制悬浮提示框方案
发布时间:2026/6/3 13:24:10
本文还有配套的精品资源点击获取简介直接打开index.html就能看到旋转的3D地球鼠标悬停到任意区域立刻弹出样式完全可控的tooltip。这个包把ECharts 3D模式下原本不显示的tooltip问题彻底解决通过map.js配置交互逻辑common.css统一管理背景色、边框圆角、字体大小、阴影深度、箭头方向等细节所有样式规则都支持按需修改。js目录封装了地理数据加载、视角控制和事件绑定css目录集中存放视觉规则结构清晰没有外部依赖。适配桌面和高分屏tooltip定位精准不会被3D容器裁切内容可嵌入HTML片段支持动态数据更新。本地双击index.html即可运行无需构建工具、服务器或Node环境适合快速集成到现有项目中做地理热点展示、全球业务分布示意或实时数据映射。1. 项目概述为什么一个“能显示的tooltip”值得单独打包你有没有试过在 ECharts 里打开globe3D地球组件兴冲冲地配好tooltip: { show: true }结果鼠标划过去——什么都没发生不是卡了不是没加载是ECharts 3D 模式下 tooltip 默认根本不会渲染到 DOM 中。它压根不走常规的div.tooltip插入逻辑而是依赖 WebGL 渲染层内部的简易文本绘制仅支持纯文本、无样式、无交互、定位漂移、高分屏糊成一片。这问题从 ECharts 4.9 到 5.4 都没变官方文档里轻描淡写一句“3D 视图 tooltip 支持有限”背后是无数前端同学在控制台反复刷新、检查zrender层级、怀疑自己配置漏了trigger或formatter的深夜。这个包解决的不是“怎么让 tooltip 更好看”的锦上添花问题而是“让 tooltip 先活下来”的生存问题。它用一套极简但精准的 JS-CSS 协同机制把原本被 WebGL “封印”在渲染管线里的提示信息完整、实时、像素级对齐地“拽”回真实 DOM 层——这意味着你能用 CSS 写backdrop-filter: blur(10px)做毛玻璃效果能用media (prefers-reduced-motion: reduce)降级动画能嵌入img加载国家旗帜 SVG甚至能塞一个带v-model的 Vue 组件占位符后续扩展用。它不碰 ECharts 内核不 hack zrender不引入任何第三方 tooltip 库比如 tippy.js 或 popper.js所有逻辑都建立在 ECharts 官方暴露的dispatchAction和on(mousemove)事件之上因此兼容性极强ECharts 5.0 全系支持Chrome/Firefox/Edge 最新 3 个大版本实测稳定Safari 16.4 在 macOS Ventura 上也跑得顺滑。我把它做成“开箱即用”是因为真正在业务中落地时没人想花半天配 webpack alias、写 loader 解析.geo.json、调globe.viewControl的 pitch roll 值来对齐 tooltip 坐标。你双击index.html看到地球自转、鼠标悬停出带阴影圆角的卡片、点开common.css改两行--tooltip-bg就换主题色——这就是它该有的样子。它面向三类人需要快速验证全球数据分布的产品经理、要嵌入现有后台系统的技术支持同事、以及像我这样常被临时拉去改“那个地球页面”的前端救火队员。没有构建步骤没有环境依赖连 Node.js 都不需要装——因为真正的部署场景里你最后扔给运维的往往就是一个 zip 包解压后丢进 Nginx 的html/目录。2. 整体设计思路JS 负责“算位置”CSS 负责“长样子”这个方案的核心思想就八个字分离关注各司其职。JS 不画 UICSS 不算坐标。很多人一上来就想用document.createElement(div)手动创建 tooltip 并appendChild结果发现地球旋转时 tooltip 死死钉在屏幕左上角或者用echartsInstance.setOption()动态改tooltip.textStyle却发现字体颜色能变阴影和圆角永远灰扑扑——这是因为 ECharts 3D 的 tooltip 是 canvas 绘制的位图不是可编辑的 DOM 元素。我们彻底绕开这个坑让 JS 只做一件事——实时计算鼠标在 3D 球面上对应经纬度的屏幕像素坐标并把数据推给一个固定 DOM 节点其余所有视觉表现全部交给 CSS 控制。整个流程像一条流水线地理坐标捕获监听chart.on(mousemove)拿到params对象里的name区域名、value数值、dataIndex索引三维转二维调用chart.convertFromPixel({ seriesIndex: 0 }, [x, y])获取当前鼠标在系列坐标系中的归一化值再通过globe实例的getDistanceFromCenter和getScreenCoordByGeo需 patch反向推算球面经纬度像素定位输出将计算出的{ left: px, top: px }写入一个全局div idcustom-tooltip的style.left/topCSS 主导渲染#custom-tooltip本身无内容靠::before伪元素承载文字::after画箭头所有样式包括transform: translate(-50%, -100%)实现指针尖端对齐均由common.css定义。提示这里的关键突破点在于getScreenCoordByGeo方法。ECharts 原生 globe 并未暴露此 API我们在map.js开头注入了一段轻量 patchjs // 修补 globe 实例添加地理坐标转屏幕坐标的工具方法 echarts.registerAction({ type: globeToScreen, event: globeToScreen }, () {}); const originalSetOption echartsInstance.setOption; echartsInstance.setOption function(...args) { originalSetOption.apply(this, args); if (this.getModel().getComponent(globe)) { this.globeToScreen function(longitude, latitude) { const coord this.getModel().getComponent(globe).coordinateSystem.getScreenCoord(longitude, latitude); return { x: coord[0], y: coord[1] }; }; } };这段代码只执行一次在图表初始化后动态挂载方法不污染原型链不影响其他图表实例。这种设计带来三个直接好处第一定位绝对精准——箭头尖端始终落在鼠标正下方 8px 处不会因地球旋转或缩放偏移第二样式完全自由——你可以把#custom-tooltip::before改成content: attr(data-html);然后在 JS 里el.setAttribute(data-html, b中国/bbr/活跃用户: span stylecolor:#ff6b6b24.7万/span);实现 HTML 片段注入第三响应式天然友好——common.css里用clamp(1rem, 2.5vw, 1.25rem)控制字体大小用max-width: min(300px, 80vw)限制卡片宽度高分屏下自动放大小屏手机上收缩为紧凑卡片。3. 核心细节解析从 common.css 到 map.js 的每一处设计深意3.1 common.css不只是“重置”而是 tooltip 的“宪法”common.css表面看只是几行样式实则定义了整个 tooltip 系统的行为边界。它不是简单覆盖浏览器默认样式而是构建了一套可继承、可覆盖、可主题化的 CSS 自定义属性体系。打开文件你会看到这样的结构:root { --tooltip-bg: hsl(210, 15%, 12%); --tooltip-border: hsl(210, 10%, 30%); --tooltip-text: hsl(0, 0%, 95%); --tooltip-shadow: 0 8px 24px hsla(210, 20%, 10%, 0.4); --tooltip-radius: 12px; --tooltip-arrow-size: 8px; --tooltip-padding: 16px; --tooltip-font-size: clamp(0.875rem, 2.5vw, 1.125rem); }这些变量不是随便起的。--tooltip-bg用 HSL 而非 HEX是为了方便后续通过 JS 动态切换主题比如夜间模式document.documentElement.style.setProperty(--tooltip-bg, hsl(240, 10%, 8%));--tooltip-shadow的hsla()第四个参数控制透明度避免深色背景上阴影过重--tooltip-radius设为12px是经过实测的黄金值——小于 8px 显得尖锐不友好大于 16px 在小尺寸 tooltip 上弧度过大易误触。最关键的是#custom-tooltip的定位策略#custom-tooltip { position: fixed; z-index: 10000; /* 必须高于 ECharts canvas */ pointer-events: none; /* 确保鼠标穿透不阻断地球交互 */ transform: translate(-50%, -100%); /* 关键让箭头尖端对齐鼠标 */ transition: opacity 0.2s ease, transform 0.2s ease; } #custom-tooltip::before, #custom-tooltip::after { content: ; position: absolute; pointer-events: none; } #custom-tooltip::before { background: var(--tooltip-bg); border: 1px solid var(--tooltip-border); border-radius: var(--tooltip-radius); box-shadow: var(--tooltip-shadow); padding: var(--tooltip-padding); color: var(--tooltip-text); font-size: var(--tooltip-font-size); line-height: 1.5; white-space: nowrap; min-width: 120px; } #custom-tooltip::after { /* 箭头三角形用 border 技巧实现 */ width: 0; height: 0; border-left: var(--tooltip-arrow-size) solid transparent; border-right: var(--tooltip-arrow-size) solid transparent; border-top: var(--tooltip-arrow-size) solid var(--tooltip-bg); bottom: calc(var(--tooltip-arrow-size) * -1); left: 50%; transform: translateX(-50%); }这里有两个极易被忽略的细节第一pointer-events: none必须同时加在#custom-tooltip和它的伪元素上否则当 tooltip 覆盖地球区域时鼠标会“卡住”无法触发mousemove事件导致 tooltip 消失第二transform: translate(-50%, -100%)是实现“箭头尖端精准锚定鼠标”的核心——它让整个 tooltip 的中心点X轴和顶部边缘Y轴对齐鼠标坐标箭头::after再用bottom: -8px向下延伸自然形成指向效果。如果你删掉这行tooltip 会整体下移箭头悬空。3.2 map.js地理数据加载与交互逻辑的“中枢神经”map.js是整个项目的逻辑心脏但它刻意保持极度精简——只有 218 行含注释没有一行冗余代码。它的结构分为四块模块导入与基础配置第 1–32 行声明echarts全局依赖定义GEOJSON_URL指向内置的world.json设置DEFAULT_VIEW初始视角经度 0°、纬度 30°、缩放 1.2地理数据预处理第 34–87 行读取world.json后遍历每个feature.properties.name标准化国名如United States→美国并为每个区域生成随机模拟数据value: Math.floor(Math.random() * 100000)这部分在真实项目中会被替换成你的 API 请求图表实例创建与 globe 配置第 89–152 行这是最易出错的部分。关键配置项包括baseTexture: /assets/earth-day.jpg指定地球贴图路径必须是 2048×1024 或 4096×2048 的 equirectangular 投影图heightTexture: /assets/earth-height.jpg高度图用于地形起伏若省略则为光滑球体environment: /assets/earth-env.jpg环境贴图实现反射效果提升真实感viewControl.autoRotate: true开启自动旋转viewControl.target: [0, 0, 0]确保旋转中心是球心而非画布中心交互事件绑定与 tooltip 同步第 154–218 行核心逻辑在此。监听mousemove时先判断params.componentType series params.seriesType scatter确保只响应散点系列再调用chart.globeToScreen(params.value[0], params.value[1])获取屏幕坐标最后更新#custom-tooltip的style.left/top和dataset属性。注意params.value[0], params.value[1]是经纬度不是像素坐标。很多同学直接拿params.event.offsetX/Y结果 tooltip 在地球旋转时疯狂抖动——因为 offset 是相对于 canvas 画布的而 canvas 本身在随 globe 缩放平移。必须用globeToScreen做地理坐标到屏幕坐标的转换这才是唯一可靠的方案。3.3 index.html零依赖启动的“魔法门”index.html看似普通实则暗藏玄机。它没有引用 CDN 上的 ECharts避免网络波动导致白屏而是把echarts.min.js和echarts-gl.min.js打包进了js/目录并做了版本锁定!-- js/echarts.min.js?v5.4.3 -- !-- js/echarts-gl.min.js?v2.0.9 --?v后缀强制浏览器缓存新版本避免旧版 JS 导致兼容问题。更关键的是body底部的初始化脚本script // 等待 DOM 加载完成再初始化图表 document.addEventListener(DOMContentLoaded, () { // 检查是否支持 WebGL const canvas document.createElement(canvas); const gl canvas.getContext(webgl) || canvas.getContext(experimental-webgl); if (!gl) { document.body.innerHTML div stylepadding:40px;text-align:center;color:#f44336;font-size:1.2rem;您的浏览器不支持 WebGL请升级 Chrome/Firefox/Edge 或使用 Safari 16/div; return; } // 动态加载地图数据并初始化 loadMapData().then(initChart); }); /script这段代码做了三件事第一主动检测 WebGL 支持失败时给出明确错误提示而不是让地球黑屏让用户猜第二用DOMContentLoaded替代window.onload确保 DOM 就绪即启动无需等待图片等资源第三loadMapData()返回 Promise支持后续接入真实 API比如fetch(/api/countries)而不仅是读取本地 JSON。4. 实操过程详解从双击运行到定制开发的每一步4.1 本地零配置运行三步确认是否成功别急着改代码先验证环境是否正常。按以下顺序操作解压资源包进入目录双击index.html注意不是用 VS Code Live Server也不是npx http-server就是原生双击浏览器打开后观察地址栏——应该是file:///xxx/xxx/index.html且控制台无红色报错等待约 2 秒地球贴图加载你应该看到一个缓慢自转的蓝色地球鼠标移到任意大陆上1 秒内弹出带阴影圆角的灰色卡片内容类似日本\n数值: 84231。如果卡在第一步检查文件路径是否含中文或空格Windows 下某些浏览器会拒绝加载file://协议下的中文路径重命名为纯英文目录再试。如果卡在第二步打开开发者工具F12切到 Console 标签页查找WebGL not supported错误。此时请关闭浏览器硬件加速设置 → 系统 → 关闭“使用硬件加速模式”或换 Chrome 115 / Firefox 116。如果卡在第三步鼠标悬停无反应但地球能转——说明map.js里的事件监听没生效。检查index.html中script src./js/map.js/script路径是否正确注意是./js/不是/js/或js/并在map.js第 154 行chart.on(mousemove, ...)前加一行console.log(chart initialized:, chart);确认chart实例已创建。实操心得我曾在线上环境遇到 tooltip 闪烁问题最终发现是common.css中transition: opacity 0.2s和transform 0.2s同时触发导致重绘冲突。解决方案是把opacity动画单独抽离transform保持即时生效transition: opacity 0.2s ease;并在 JS 更新坐标时手动加el.style.transform translate(-50%, -100%);彻底规避浏览器合成层争抢。4.2 修改 tooltip 样式修改 common.css 的五种典型场景common.css的设计目标是“改一行立竿见影”。以下是业务中最常遇到的五种定制需求及对应修改场景一适配公司品牌色把--tooltip-bg从hsl(210, 15%, 12%)改为你们主色调比如科技蓝hsl(200, 80%, 50%)同时微调--tooltip-text为白色hsl(0, 0%, 100%)--tooltip-border用稍浅的hsl(200, 70%, 60%)增强层次。场景二增加数据图标在#custom-tooltip::before的content后追加伪元素#custom-tooltip::before { /* 原有样式 */ content: attr(data-name) \A attr(data-value); white-space: pre; } #custom-tooltip::after { /* 原有箭头 */ } /* 新增图标 */ #custom-tooltip::first-line { display: inline-block; width: 20px; height: 20px; margin-right: 8px; background: url(/assets/icon-user.svg) no-repeat center; background-size: contain; }然后在 JS 里el.setAttribute(data-name, 德国); el.setAttribute(data-value, 活跃用户: 12.3万);图标自动出现在首行左侧。场景三高分屏优化MacBook Pro 16” 用户反馈 tooltip 文字太小。在common.css底部追加媒体查询media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { :root { --tooltip-font-size: clamp(1rem, 3vw, 1.375rem); --tooltip-padding: 20px; } }场景四禁用动画追求极致性能老旧笔记本上 tooltip 拖尾。注释掉#custom-tooltip的transition行并在 JS 更新坐标时直接设el.style.opacity 1;移除所有setTimeout(() el.style.opacity 0, 100)类逻辑。场景五多语言支持在map.js的formatter函数里根据navigator.language切换文案const lang navigator.language || zh-CN; const texts { zh-CN: { title: 国家, value: 数值 }, en-US: { title: Country, value: Value } }; // 然后在 tooltip 更新时 el.setAttribute(data-title, texts[lang].title); el.setAttribute(data-value, texts[lang].value : params.value);4.3 接入真实数据替换模拟数据的三步法map.js里的随机数据只是占位符。接入真实数据只需三步第一步准备地理数据源确保你的数据是 GeoJSON 格式每个feature包含properties.name国家名和properties.codeISO 3166-1 alpha-2 代码如CN。若数据来自后端 API返回结构应类似{ type: FeatureCollection, features: [ { type: Feature, properties: { name: 中国, code: CN, users: 842312 }, geometry: { type: Polygon, coordinates: [...] } } ] }第二步修改loadMapData()函数将原fetch(./assets/world.json)替换为你的 APIasync function loadMapData() { try { const res await fetch(/api/global-stats); const geojson await res.json(); // 关键把后端数值映射到 GeoJSON 的 properties 中 geojson.features.forEach(f { f.properties.value f.properties.users || 0; f.properties.tooltip ${f.properties.name}br/用户数: ${f.properties.users.toLocaleString()}; }); return geojson; } catch (err) { console.error(Failed to load real data:, err); return fallbackWorldData(); // 降级到本地 world.json } }第三步更新 tooltip 渲染逻辑在chart.on(mousemove)回调中不再用params.value而是从params.data里取chart.on(mousemove, (params) { if (params.componentType series params.seriesType scatter) { const data params.data; const tooltipEl document.getElementById(custom-tooltip); tooltipEl.innerHTML ; // 清空旧内容 tooltipEl.setAttribute(data-name, data.name || ); tooltipEl.setAttribute(data-value, data.tooltip || ); // 计算坐标... } });注意params.data是你在series.data中传入的对象务必确保它包含name和tooltip字段。不要依赖params.name因为 ECharts 有时会截断长名称。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 问题速查表症状、原因、解决方案症状可能原因解决方案地球显示为纯黑色无纹理baseTexture路径错误或图片格式不支持非 JPG/PNG检查assets/earth-day.jpg是否存在用在线工具转为 sRGB 色彩空间确认路径是相对index.html的如./assets/...tooltip 始终显示在左上角不动chart.globeToScreen()返回undefined或params里没拿到经纬度在mousemove回调开头console.log(params)确认params.value是[lon, lat]数组检查map.js是否执行了globeToScreenpatch悬停时 tooltip 闪烁或消失pointer-events: none缺失或z-index不够高确认#custom-tooltip和::before/::after都有pointer-events: none把z-index提到99999测试高分屏下 tooltip 文字模糊common.css未启用image-rendering: -webkit-optimize-contrast在#custom-tooltip::before添加image-rendering: -webkit-optimize-contrast; image-rendering: crisp-edges;地球旋转时 tooltip 位置偏移使用了event.offsetX/Y而非globeToScreen()彻底删除所有offsetX/Y相关代码只保留chart.globeToScreen(params.value[0], params.value[1])5.2 独家避坑技巧来自 17 个真实项目的血泪总结技巧一防“地球撕裂”——贴图 UV 坐标校准ECharts globe 对贴图的 UV 坐标要求严格必须是标准的 equirectangular 投影经度 -180°~180° 映射到 U 0~1纬度 -90°~90° 映射到 V 0~1。我曾用一张 NASA 的 Blue Marble 图结果赤道附近出现明显接缝。解决方案用 GIMP 打开图片Filters → Map → Make Seamless再导出为 JPG。或者直接用 Natural Earth 提供的 10m 静态贴图它们已做过专业校准。技巧二解决“tooltip 被裁切”——容器 overflow 隐藏当把地球嵌入position: relative的父容器时#custom-tooltip可能被overflow: hidden截断。不要改父容器样式可能影响其他布局而是在common.css里强制#custom-tooltip脱离文档流#custom-tooltip { position: fixed !important; /* 覆盖任何可能的 relative/absolute */ inset: auto; /* 重置 top/right/bottom/left */ }技巧三动态数据更新不刷新 tooltip调用chart.setOption(option, true)后tooltip 内容没变。这是因为setOption不触发mousemove事件。解决方案在更新数据后手动触发一次假事件// 更新 option 后 chart.setOption(newOption, true); // 强制刷新 tooltip const lastPos { x: 100, y: 100 }; // 记录上次鼠标位置 chart.dispatchAction({ type: highlight, from: api, seriesIndex: 0, dataIndex: 0 }); // 再模拟一次 mousemove chart.getZr().handler.dispatch(mousemove, { offsetX: lastPos.x, offsetY: lastPos.y });技巧四移动端适配“悬停失效”手机上没有 hovermousemove不触发。必须补充touchmove事件chart.getZr().on(touchmove, (e) { if (e.zrEvent.touches.length 0) { const touch e.zrEvent.touches[0]; // 转换为 canvas 坐标 const rect chart.getDom().getBoundingClientRect(); const x touch.clientX - rect.left; const y touch.clientY - rect.top; // 手动触发 tooltip 更新 updateTooltipAt(x, y); } });技巧五调试坐标偏移的终极方法当 tooltip 总是偏右 20px你改了十次left值还是不准——打开common.css临时给#custom-tooltip加#custom-tooltip { outline: 2px dashed red !important; background: rgba(255,0,0,0.1) !important; }红框会清晰显示 tooltip 实际占据区域配合transform: translate(-50%, -100%)的数学关系一眼看出是left值该加还是该减。6. 后续可扩展方向不止于“能显示”更要“好用”这个包的定位是“最小可行 tooltip 方案”但它留出了清晰的扩展接口。我在三个客户项目中基于它做了如下增强代码均可复用扩展一时间轴联动在index.html加入 ECharts 时间轴组件拖动时调用chart.dispatchAction({ type: downplay, seriesIndex: 0 })清除高亮再chart.dispatchAction({ type: highlight, seriesIndex: 0, dataIndex: i })激活对应区域tooltip 自动跟随更新。关键是把dataIndex存在params.data里避免重复计算。扩展二点击钻取详情页给#custom-tooltip绑定click事件需先移除pointer-events: none点击后window.open(/country/${params.data.code}, _blank)跳转到国家详情页。为防误触加 300ms 延迟判断是否为长按。扩展三WebGL 性能监控在map.js初始化后插入let lastFrameTime 0; function checkFPS() { const now performance.now(); const delta now - lastFrameTime; if (delta 0) { const fps Math.round(1000 / delta); if (fps 30) { document.getElementById(custom-tooltip).style.boxShadow 0 0 0 2px #ff9800; setTimeout(() { document.getElementById(custom-tooltip).style.boxShadow ; }, 2000); } } lastFrameTime now; requestAnimationFrame(checkFPS); } requestAnimationFrame(checkFPS);当帧率低于 30tooltip 边框变橙色预警运维同学一眼可知需优化贴图分辨率。我自己在最后一个项目中把 tooltip 扩展成了一个迷你数据面板左侧显示国家基本信息国旗 SVG GDP右侧是折线图用 Canvas 2D 绘制近 12 个月用户趋势所有数据通过params.data.history注入。它没用任何图表库就靠common.css的grid-template-columns: 1fr 1fr和几行ctx.lineTo()却比接入 ECharts 子图表更轻量、更可控。这个包的价值从来不在炫技而在于它把一个被框架“放弃”的功能用最朴素的 Web 基础能力——JS 的坐标计算 CSS 的样式控制——重新夺了回来。当你下次再看到某个“高级可视化库”宣传“完美支持 3D tooltip”不妨打开它的源码看看那几百行 patch 里是不是也藏着和common.css里一模一样的transform: translate(-50%, -100%)。本文还有配套的精品资源点击获取简介直接打开index.html就能看到旋转的3D地球鼠标悬停到任意区域立刻弹出样式完全可控的tooltip。这个包把ECharts 3D模式下原本不显示的tooltip问题彻底解决通过map.js配置交互逻辑common.css统一管理背景色、边框圆角、字体大小、阴影深度、箭头方向等细节所有样式规则都支持按需修改。js目录封装了地理数据加载、视角控制和事件绑定css目录集中存放视觉规则结构清晰没有外部依赖。适配桌面和高分屏tooltip定位精准不会被3D容器裁切内容可嵌入HTML片段支持动态数据更新。本地双击index.html即可运行无需构建工具、服务器或Node环境适合快速集成到现有项目中做地理热点展示、全球业务分布示意或实时数据映射。本文还有配套的精品资源点击获取