指定形状滚动词云图:ECharts自定义形状与Canvas 3D球体实现

发布时间:2026/10/18 18:18:37

指定形状滚动词云图:ECharts自定义形状与Canvas 3D球体实现 简介这是一套面向前端开发者与数据可视化爱好者的词云图实现合集聚焦自定义形状与3D滚动效果适合需要为页面增添动态视觉呈现、又不想依赖重型图表库的读者参考。压缩包共8个文件约78KB包含3个js脚本、2个html页面、1个css样式、1张示例图片及1份说明文本分别承担核心逻辑、演示入口、样式控制与素材支撑。内容覆盖三条技术路线基于echarts按自定义图片轮廓生成词云纯js实现的3D球体滚动词云并支持鼠标悬浮定向滚动以及html5 Canvas绘制的3D球形标签云可自由缩放画布尺寸。代码结构轻量便于直接嵌入现有项目或二次改造。目前已有4299人学习下载可作为词云可视化入门与效果对比的实用参考。1. 指定形状滚动词云图从 ECharts 自定义形状到 Canvas 3D 球体一套资源拆明白大屏项目里最容易被产品经理一句话推翻的往往不是数据接口而是那颗“能不能让词云转起来、还得是个球”的需求。指定形状滚动词云图这套资源本质上就是解决三件事用 ECharts 的maskImage把词云塞进任意轮廓、用 JS 版 3D 球形滚动做出空间旋转感、再用原生 Canvas 手写一个可缩放的 3D 球体词云。它适合做大屏可视化、活动页氛围词云、品牌关键词展示的前端同学也适合想搞懂词云底层布局算法、不想被第三方库版本锁死的开发者。资源里三套实现各有取舍下面按“能跑起来”的顺序拆。2. ECharts 自定义形状词云maskImage 与 layoutAnimation 怎么配ECharts 本身不带词云得挂echarts-wordcloud扩展。指定形状的核心不是改布局算法而是给词云一个“遮罩轮廓”——扩展内部会把maskImage解析成像素矩阵再让每个词去碰撞检测落在轮廓内的才保留。理解这一点后面调参就不会瞎试。2.1 引入依赖与最小可运行结构先确认版本匹配这是血泪经验echarts-wordcloud2.x 对应 ECharts 5.x1.x 对应 ECharts 4.x混用会出现词全部堆在左上角、或者干脆不渲染。常见做法是用 CDN 或本地包二选一别同时引两份。!-- 页面容器宽高必须显式给词云不会自己撑开 -- div idwordcloud stylewidth: 800px; height: 600px;/div script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts-wordcloud2.1.0/dist/echarts-wordcloud.min.js/script逻辑说明echarts-wordcloud是作为 ECharts 的扩展注册的必须在 ECharts 之后加载否则series.type: wordCloud会报未知类型。参数上容器宽高用 CSS 写死或用 JS 在初始化前设置ECharts 初始化时读取的是渲染那一刻的尺寸容器为 0 会导致画布空白。2.2 用 maskImage 指定形状的关键配置maskImage接收一个 Image 对象或图片 URL白色区域为可放置区透明或黑色区域被排除。很多人翻车在图片没加载完就setOption导致遮罩失效退化成矩形。const chart echarts.init(document.getElementById(wordcloud)); // 先加载遮罩图再渲染避免遮罩未就绪 const mask new Image(); mask.crossOrigin anonymous; // 跨域图必须加否则 canvas 被污染 mask.src ./mask-heart.png; mask.onload () { chart.setOption({ series: [{ type: wordCloud, shape: circle, // 内置形状兜底maskImage 优先 maskImage: mask, // 自定义轮廓白色为有效区 width: 800, height: 600, sizeRange: [14, 72], // 字号区间跨度太大会挤爆轮廓 rotationRange: [0, 0], // 指定形状时建议不旋转旋转会破坏轮廓贴合 rotationStep: 0, gridSize: 8, // 词间距越小越密越小也越慢 drawOutOfBound: false, // 超出画布的词直接丢弃防止溢出 layoutAnimation: true, // 布局动画首次渲染有“聚拢”效果 textStyle: { fontFamily: sans-serif, fontWeight: bold, color: () { // 随机色也可换成按权重映射色带 const palette [#5B8FF9, #61DDAA, #F6BD16, #F08BB4]; return palette[Math.floor(Math.random() * palette.length)]; } }, data: [ { name: 可视化, value: 120 }, { name: 大屏, value: 98 }, { name: 词云, value: 86 } // 其余数据省略 ] }] }); };逻辑说明maskImage生效时shape会被忽略但保留一个内置形状作为图片加载失败时的兜底更稳。rotationRange设为[0,0]是因为旋转后的词包围盒变大容易戳出轮廓边界视觉上“形状感”就没了。gridSize是性能与密度的平衡点8 到 12 之间比较常用低于 6 在词量上千时会明显卡顿。参数补充sizeRange的下限别低于 12否则小词看不清上限取决于轮廓最宽处心形、星形这类尖角轮廓上限要压到 60 以内。drawOutOfBound: false配合maskImage使用能避免词被裁一半挂在边缘。2.3 数据权重与颜色映射的落地写法词云好不好看一半在数据分布。如果 value 差距过大会出现一两个巨词加一堆蚂蚁词。常见做法是先做一次归一化或对数压缩。// 原始数据 value 从 1 到 10000直接映射会极端 const raw [/* {name, value} ... */]; // 对数压缩保留相对差异又不会一家独大 const compressed raw.map(d ({ name: d.name, value: Math.log10(d.value 1) * 100 })); // 按压缩后的值映射字号区间交给 sizeRange 处理即可 chart.setOption({ series: [{ data: compressed }] });逻辑说明Math.log10(value 1)把长尾分布拉平加 1 是防止 value 为 0 时取对数得到负无穷。压缩后再交给sizeRange做线性映射视觉上大小差异更可控。颜色如果要做成按权重渐变把color回调改成读取params.data.value再查色带比纯随机更有信息量。3. JS 版 3D 球形滚动透视投影与旋转矩阵的轻量实现ECharts 词云是 2D 布局做不出“球在转”的空间感。JS 版 3D 球形滚动的思路是把每个词当成球面上的一个点用球坐标生成初始位置每帧绕 Y 轴或任意轴旋转再用透视投影把 3D 坐标压到 2D 屏幕坐标最后按 z 值决定字号和透明度。这套逻辑不依赖 three.js纯 DOM 或 Canvas 都能做。3.1 球面均匀取点别用随机经纬度新手最容易踩的坑是用Math.random()取经纬度结果词全挤在南北极。球面均匀分布要用 Fibonacci 球面或等面积采样。// Fibonacci 球面均匀取点n 为词数量 function spherePoints(n) { const points []; const offset 2 / n; const increment Math.PI * (3 - Math.sqrt(5)); // 黄金角 for (let i 0; i n; i) { const y i * offset - 1 offset / 2; // y 从 -1 到 1 const r Math.sqrt(1 - y * y); // 当前纬度圈半径 const phi i * increment; points.push({ x: Math.cos(phi) * r, y: y, z: Math.sin(phi) * r }); } return points; }逻辑说明黄金角π * (3 - √5)保证相邻点经度差恒定y均匀递增保证纬度方向等面积两者结合得到近似均匀的球面分布。返回的是单位球坐标后续乘以半径即可。参数上n就是词的数量词越多球面越密但 DOM 节点超过 300 个后滚动会掉帧建议控制在 150 到 250 之间。3.2 旋转与透视投影每帧只做矩阵乘法旋转用绕 Y 轴和 X 轴的两个旋转矩阵组合透视投影用简单的scale focal / (focal z)。const RADIUS 260; // 球半径单位 px const FOCAL 600; // 焦距越大透视越弱 let angleX 0.002; // 每帧 X 轴增量 let angleY 0.004; // 每帧 Y 轴增量 function rotate(point, ax, ay) { // 绕 X 轴 const cosX Math.cos(ax), sinX Math.sin(ax); let y point.y * cosX - point.z * sinX; let z point.y * sinX point.z * cosX; // 绕 Y 轴 const cosY Math.cos(ay), sinY Math.sin(ay); let x point.x * cosY z * sinY; z -point.x * sinY z * cosY; return { x, y, z }; } function project(p) { const scale FOCAL / (FOCAL p.z * RADIUS); return { x: p.x * RADIUS * scale, y: p.y * RADIUS * scale, scale: scale, z: p.z }; }逻辑说明rotate先绕 X 再绕 Y顺序不同结果不同但视觉上都能接受固定一种即可。project里p.z * RADIUS把单位坐标还原成像素深度FOCAL控制近大远小的强度600 左右比较自然调到 300 会夸张变形调到 1500 接近正交投影、立体感变弱。每帧对每个词调用这两个函数更新transform: translate3d和fontSize。3.3 用 requestAnimationFrame 驱动并处理缩放滚动动画用requestAnimationFrame驱动缩放则监听滚轮或容器尺寸变化改RADIUS和FOCAL的比值。const words document.querySelectorAll(.word); const basePoints spherePoints(words.length); function animate() { angleX 0.002; angleY 0.004; basePoints.forEach((bp, i) { const rotated rotate(bp, angleX, angleY); const proj project(rotated); const el words[i]; el.style.transform translate3d(${proj.x}px, ${proj.y}px, 0) scale(${proj.scale}); el.style.opacity Math.max(0.25, proj.scale - 0.3); el.style.zIndex Math.round(proj.scale * 100); }); requestAnimationFrame(animate); } animate();逻辑说明translate3d触发 GPU 合成比改left/top流畅。opacity下限设 0.25 是防止背面的词完全消失、显得球体“缺一块”。zIndex按 scale 排序保证前面的词压住后面的词。缩放时改RADIUS即可但FOCAL要同步按比例调否则透视强度会变。注意DOM 版词云在词量超过 300 或低端移动设备上会明显掉帧这种场景直接换 Canvas 版别硬扛。4. HTML Canvas 缩放 3D 球形滚动把 DOM 换成绘制调用Canvas 版和 DOM 版数学部分完全一样区别在于渲染不再操作节点样式而是每帧clearRect后按 z 排序逐个fillText。好处是词量可以上到 800 甚至更多缩放也更顺滑代价是文字样式控制不如 CSS 灵活点击交互要自己做命中检测。4.1 初始化画布与高清屏适配Canvas 在高分屏上不处理devicePixelRatio会糊这是最常见的“为什么我的字发虚”原因。const canvas document.getElementById(cloud); const ctx canvas.getContext(2d); const DPR window.devicePixelRatio || 1; function resize() { const rect canvas.getBoundingClientRect(); canvas.width rect.width * DPR; canvas.height rect.height * DPR; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); // 缩放坐标系后续按 CSS 像素写 } window.addEventListener(resize, resize); resize();逻辑说明canvas.width设成 CSS 宽度的 DPR 倍再用setTransform把绘制坐标系缩回 CSS 像素这样后续fillText的坐标和字号都按视觉尺寸写不用手动乘 DPR。resize时重设尺寸会清空画布所以要在动画循环里持续重绘不能只画一次。4.2 每帧排序与绘制z 值决定一切Canvas 没有 zIndex必须手动按 z 从远到近排序远的先画、近的后画才能正确遮挡。function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); const items basePoints.map((bp, i) { const rotated rotate(bp, angleX, angleY); const proj project(rotated); return { proj, word: wordList[i] }; }); // 按 z 从大到小远到近排序 items.sort((a, b) b.proj.z - a.proj.z); items.forEach(({ proj, word }) { const fontSize Math.max(10, 22 * proj.scale); ctx.font bold ${fontSize}px sans-serif; ctx.fillStyle word.color; ctx.globalAlpha Math.max(0.25, proj.scale - 0.3); ctx.textAlign center; ctx.textBaseline middle; ctx.fillText(word.name, proj.x, proj.y); }); angleX 0.002; angleY 0.004; requestAnimationFrame(draw); } draw();逻辑说明sort每帧执行词量 500 时排序开销可接受超过 1000 建议改用分桶或只对可见词排序。fontSize用proj.scale缩放保证近大远小。globalAlpha控制背面词淡出和 DOM 版一致。textAlign和textBaseline设为中心让坐标对应词的中心点否则位置会偏。4.3 缩放与拖拽交互的接入缩放改RADIUS拖拽改angleX/angleY的增量方向两者都在动画循环外更新状态即可。let dragging false, lastX 0, lastY 0; canvas.addEventListener(mousedown, e { dragging true; lastX e.clientX; lastY e.clientY; }); canvas.addEventListener(mousemove, e { if (!dragging) return; const dx e.clientX - lastX; const dy e.clientY - lastY; angleY dx * 0.005; // 水平拖拽改 Y 轴旋转 angleX dy * 0.005; // 垂直拖拽改 X 轴旋转 lastX e.clientX; lastY e.clientY; }); canvas.addEventListener(mouseup, () dragging false); canvas.addEventListener(wheel, e { e.preventDefault(); RADIUS Math.min(500, Math.max(120, RADIUS - e.deltaY * 0.3)); }, { passive: false });逻辑说明拖拽时直接累加角度松手后动画循环继续按原增量自转形成“甩一下继续转”的手感。wheel里preventDefault阻止页面滚动passive: false是必须的否则浏览器忽略阻止。RADIUS限制在 120 到 500太小词叠在一起太大超出画布。5. 避坑与排查词云不显示、卡顿、形状失效的常见原因5.1 词云整块空白控制台无报错现象容器有尺寸数据也有但画布一片空白。原因通常是echarts-wordcloud没加载成功或者series.type拼写错误。解决在setOption前打印echarts.version和扩展是否注册确认type: wordCloud大小写正确扩展脚本 404 时浏览器不一定报错。5.2 maskImage 设了但形状没生效现象词云还是矩形分布轮廓没起作用。原因有三图片跨域导致 canvas 被污染、图片未加载完就渲染、遮罩图不是“白底有效”而是透明底。解决加crossOrigin在onload里setOption用图片编辑工具确认有效区域是纯白、其余是透明或纯黑。5.3 3D 球体滚动时词闪烁或抖动现象旋转过程中部分词位置跳变。原因每帧sort后 z 值接近的词顺序不稳定或者translate3d的亚像素取整导致。解决给 z 排序加一个稳定的次级排序键如索引DOM 版可给transform加translateZ(0)强制合成层减少重排抖动。5.4 词量一多就卡成幻灯片现象超过 200 个词后帧率骤降。原因DOM 版每帧改几百个节点的style触发大量重排重绘。解决换 Canvas 版若必须用 DOM把不动的词合并、只对可见词做动画或降低刷新频率到 30fps。5.5 缩放后文字模糊或错位现象Canvas 版放大后字发虚或点击位置对不上。原因没做 DPR 适配或getBoundingClientRect在缩放后没重新读取。解决按 4.1 的写法设canvas.width和setTransform交互坐标用e.clientX - rect.left换算别直接用offsetX。6. 进阶把三套实现揉进一个可切换的组件实际项目里很少只用一种常见做法是封装一个容器按词量自动切换渲染模式词量小于 150 用 DOM 3D 球样式好调、可加 hover150 到 600 用 Canvas 3D 球性能稳需要指定轮廓时切 ECharts 版。切换时销毁旧实例、解绑事件避免多个requestAnimationFrame同时跑。class WordCloudSwitcher { constructor(el, data) { this.el el; this.data data; this.mode data.length 150 ? dom : canvas; this.instance null; } render() { this.destroy(); // 先销毁防止动画叠加 if (this.mode dom) { this.instance new DomSphere(this.el, this.data); } else if (this.mode canvas) { this.instance new CanvasSphere(this.el, this.data); } else { this.instance new EchartsMask(this.el, this.data); } this.instance.start(); } destroy() { if (this.instance this.instance.stop) { this.instance.stop(); // 取消 rAF、解绑事件 } this.el.innerHTML ; this.instance null; } }逻辑说明destroy里必须取消requestAnimationFrame否则旧实例的循环还在跑内存和 CPU 都下不来。mode的判断阈值可以按实测调移动端把 DOM 阈值降到 80 更稳。ECharts 版切换时调chart.dispose()而不是只清空容器。验证是否真的切换成功我一般会做三件事看performance面板里 rAF 回调数量是否只有一个在跑缩放窗口看是否只有当前实例响应连续切换 20 次看内存是否持续上涨。从那以后我每次接词云需求都强制先问清词量上限和是否需要指定轮廓再决定用哪套省得返工。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/18 18:18:36

大模型训练到推理部署全流程:微调、显存优化与LoRA实战

本来没想写这种长文,但每天都能收到同样的问题:大模型到底怎么学?是从训练入手还是先搞推理?需不需要自己从头训练一个模型?微调该学什么框架?每次答起来都很零碎,干脆花时间把这条从训练到推理…

2026/10/18 18:13:36

视频预测策略VPP2:从预测到行动,打通视觉决策与策略控制

做视觉决策的朋友多少都有过这种体验:视频预测模型的指标已经刷得不错,生成出来的未来帧也挺自然,结果一接到下游策略里,智能体该撞墙还是撞墙、该拿不到的物体还是拿不到。这不是个别现象,而是预测模型和策略模型在优…

2026/10/18 18:13:36

FPGA PWM 动态调占空比:影子寄存器怎样保证整周期更新

FPGA PWM 动态调占空比:影子寄存器怎样保证整周期更新 先复现:Windows 11 / Vivado Simulator 2022.1 行为仿真。将附录 A、B 分别保存为 pwm_shadow.sv、tb_pwm.sv,并保存第 6 节的 simulate.tcl;在已配置 Vivado 命令环境的空目…

2026/10/18 19:18:41

Java实现LSTM电力负荷预测:从理论到边缘部署

简介:本资源是一套基于LSTM算法实现电力负荷预测的Java项目源码,面向计算机、电气工程及相关专业本科生与研究生,适用于毕业设计、课程设计及期末大作业等实践场景。项目难度适中、结构清晰、注释完整,配套说明文档详实&#xff0…

2026/10/18 19:18:41

蛇形矩阵算法详解:回字形与S形实现及边界控制技巧

1. 蛇形矩阵到底是什么,为什么值得单独拎出来讲蛇形矩阵这个题,在算法练习平台里属于那种“看起来简单、写起来卡壳、写完还想再优化”的经典题型。我第一次碰到它的时候,心里想的是:不就是把数字按顺序填进一个方格吗&#xff1f…

2026/10/18 19:18:41

MFC中模拟按钮BN_CLICKED消息的三种实现方案与避坑指南

1. 从一个真实需求说起:为什么要手动模拟按钮点击做Windows桌面开发的朋友,尤其是维护过一些年代比较久远的MFC项目的,大概率都遇到过这样一种场景:界面上有个按钮,用户点击它的时候一切正常,但某天产品经理…

2026/10/18 19:18:41

Linux下DS1302实时时钟的GPIO模拟驱动与调试指南

简介:围绕 Linux 下 DS1302 实时时钟(RTC)驱动开发与验证,资源面向嵌入式驱动开发者、内核初学者及需要为 ARM 或开发板适配 RTC 功能的工程师,可用于课程实验、产品原型或驱动上手练习。压缩包共 22 个文件&#xff0…

2026/10/18 19:18:41

从RoboMaster硬件讲义V0.2.1,看机器人硬件学习的体系化之路

1. 版本号背后:为什么硬件知识需要“讲义化”拿到这份《Robomaster硬件基础讲义V0.2.1》的时候,我第一反应不是去看目录,而是盯着那个“V0.2.1”看了半天。做过项目的人都懂,软件迭代到 0.2.x 太正常了,但硬件知识讲义…

2026/10/18 19:13:41

安全审计工程化:从扫描报告到可验证代码资产

1. 这不是“安全扫描”,而是用代码写审计报告的硬功夫“security-audit-skill”——光看这个词,很多人第一反应是:又一个带“-”的英文组合词,大概率是某个工具命令、CI/CD里的一个stage名,或者某份技术文档里被加粗的…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/18 0:01:50

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑