惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
量子位
A
Arctic Wolf
L
Lohrmann on Cybersecurity
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
Last Week in AI
Last Week in AI
The Hacker News
The Hacker News
I
Intezer
J
Java Code Geeks
P
Privacy International News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Secure Thoughts
Cisco Talos Blog
Cisco Talos Blog
阮一峰的网络日志
阮一峰的网络日志
S
Securelist
Security Latest
Security Latest
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
Jina AI
Jina AI
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
雷峰网
雷峰网
T
Tenable Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 【当耐特】
T
Threat Research - Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
H
Help Net Security
宝玉的分享
宝玉的分享
罗磊的独立博客
Webroot Blog
Webroot Blog
月光博客
月光博客
B
Blog RSS Feed
Recorded Future
Recorded Future

姓王者的博客

Linux用户Secure Boot自主维护指南 | 姓王者的博客 MAD Bugs 已经开始——关于信息安全的军备竞赛 | 姓王者的博客 解决钉钉Dingtalk无法在Linux新版内核上启动问题-修复可执行栈错误 | 姓王者的博客 突发:GitHub 正遭受大规模 Issue 赌博广告轰炸 | 姓王者的博客 Ubuntu26.04-beta体验:坚毅浣熊! | 姓王者的博客 fakeclaw装作龙虾发贴吧 | 姓王者的博客 找回12年前的QQ记忆 | 姓王者的博客 在Linux上玩Flash网页游戏-洛克王国 | 姓王者的博客 Copilot将使用交互数据来训练 | 姓王者的博客 重要通知-请更新我的GPG公钥 | 姓王者的博客 为了自由Android | 姓王者的博客 GPL"2,3"事 | 姓王者的博客 短文-对VitePlus的一点🤏小贡献 | 姓王者的博客 Bing收录没了?亲测有效的快速恢复指南 | 姓王者的博客 解决桌面设备二维码快速识别的工具-ClipQR | 姓王者的博客 解决 Nautilus 自定义终端插件安装依赖问题 | 姓王者的博客 OpenClaw 该熄火了 | 姓王者的博客 Vite8 - 统一的基建开始 | 姓王者的博客 Astro 6 推出啦 | 姓王者的博客 ubuntu的openvpn异常暂停推送更新 | 姓王者的博客 Ubuntu 24.04 安装 Win10 虚拟机 | 姓王者的博客 ESA-后记:热爱阿里云 | 姓王者的博客 Moonbit 0.8.0 重大发布,我也要改一下我的包 | 姓王者的博客 ESA Pages 边缘开发大赛获奖 | 姓王者的博客 Astro: 优化katex,mermaid和灯箱使用 | 姓王者的博客 从edgeone迁移到esa | 姓王者的博客 出租人类:AI时代的荒诞与真实 | 姓王者的博客 Astro 5.17构建性能优化实践:从18s到13s | 姓王者的博客 Moonbit License Checker 开发使用 | 姓王者的博客 Stalux Astro博客主题自荐 | 姓王者的博客 把Hexo永久链接迁移到Astro | 姓王者的博客 再见👋 LeanCloud | 姓王者的博客 2025年终总结 | 姓王者的博客 许可合规-fancybox | 姓王者的博客 博客主题的软著下来了 | 姓王者的博客 友链图谱 - 汇聚千丝万缕的联系 | 姓王者的博客 chen-er 专为Chen式ER图打造的npm包 | 姓王者的博客 为什么我推荐你使用GPG来加密你的邮件 | 姓王者的博客 2025第三方客户端登录东北大学邮箱 | 姓王者的博客 好久没更新了,过去与未来 | 姓王者的博客 1024 重要的日子 | 姓王者的博客 再也不见Windows10 | 姓王者的博客 偷梁换柱,解决Ubuntu24.04安装Packet Tracer缺失依赖问题 | 姓王者的博客 中秋-来试试Moonbit吧 | 姓王者的博客 Obsidian使用体验 | 姓王者的博客 猪猪侠·一只老猪的逆袭 | 姓王者的博客 国庆日纪念 | 姓王者的博客 GNU 42周年,AI时代的自由精神 | 姓王者的博客 解决Linux上启动游戏总是默认English的情况 | 姓王者的博客 7x24:运维使命 | 姓王者的博客 Tauri2.x实现系统菜单导航Vue路由 | 姓王者的博客 计算机图形学-基本图形生成算法 | 姓王者的博客 数据库原理-关系数据 | 姓王者的博客 数据库原理-设计技巧 | 姓王者的博客 数据库原理E-R模型 | 姓王者的博客 旧忆 - 我曾玩过的游戏 | 姓王者的博客 再谈自由软件 | 姓王者的博客 可能解决Tauri多窗口应用阻塞问题 | 姓王者的博客 Xingwangzhe! Z-Library We miss you and we need your help | 姓王者的博客 计算机组成原理第二章 - 定点数与浮点数 | 姓王者的博客 计算机组成原理第一章 | 姓王者的博客 不小心写死循环窗口弹出了 | 姓王者的博客 美化Grub界面 | 姓王者的博客 计算机图形学-图形的表示与数据结构 | 姓王者的博客 计算机图形学绪论 | 姓王者的博客 为什么说,大学教育与社会脱节 | 姓王者的博客 VSCode Remote 远程连接服务器记录 | 姓王者的博客 解决Tauri2.x拖拽事件问题 | 姓王者的博客 新学期第一课《计算机图形学》报告 | 姓王者的博客 Tauri在GNOME46+上通知无效的临时解决方法 | 姓王者的博客 窃文者:未经授权转载我文章 | 姓王者的博客 GPG公钥分享文化 | 姓王者的博客 解决在ubuntu上,打包vscode插件问题 | 姓王者的博客 伪造squaremap的玩家显示 | 姓王者的博客 爆,沉浸式翻译泄露敏感信息 | 姓王者的博客 读书:《Free as in Freedom》——若为自由故 | 姓王者的博客 首页文章列表懒加载优化 | 姓王者的博客 Ubuntu 24.04 安装 Vivado 2018.3 | 姓王者的博客 腾讯Edgeone免费版体验 | 姓王者的博客 在 Ubuntu 上实现 Thetis FIDO U2F 密钥登录 | 姓王者的博客 Thetis物理密钥,为什么我们应该使用物理密钥 | 姓王者的博客 高考生过来看!教你精准转换录取位次! | 姓王者的博客 ubuntu无法访问windows磁盘问题 | 姓王者的博客 收信有感,防范钓鱼邮件 | 姓王者的博客 自由不止软件-记录一次zlib上传书籍 | 姓王者的博客 时隔两年,通关夺命邮差2 | 姓王者的博客 博客一周年了,竟然坚持了下来 | 姓王者的博客 Minecraft大电影:不建不散! | 姓王者的博客 是时候了解docker了! | 姓王者的博客 编译原理:LL(1)文法 | 姓王者的博客 编译原理:文法转换 | 姓王者的博客 离散数学:子群的陪集及拉格朗日定理 | 姓王者的博客 离散数学:半群,独异点 | 姓王者的博客 《人工智能生成合成内容标识办法》与个人博客--我们应该做什么? | 姓王者的博客 通识学习:形式语言与自动机,布尔代数与数进制 | 姓王者的博客 Webmapview:一个我的世界内置网页地图浏览Fabric模组 | 姓王者的博客 海岛机器人农场试玩 | 姓王者的博客 正则表达式学习 | 姓王者的博客 抓取个人博客文章目录到github主页 | 姓王者的博客 制作github贪吃蛇贡献图 | 姓王者的博客
我做了一个现代Web版本的标签云,支持图片视频Web组件 | 姓王者的博客
作者:xingwangzhe · 2026-06-10 · via 姓王者的博客

🕒 阅读时间:3 分钟 📝 字数:1193 👀 阅读量: Loading...

本文由DeepSeek润色

标签云页面Demo: https://tagscloud.needhelp.icu/

前言

最近想美化一下博客的标签云页面。我希望标签能在一个 3D 球面上旋转。搜了一圈发现现有的轮子

cong-min/TagCloud——已经是 2017 年的作品了:ES5 编写、仅支持纯文本、纯 DOM 渲染。而且我还有一些”奇思妙想”

——比如让标签云里混入图片、视频、甚至 Web Components。

于是我决定从零重构一个现代版本:@xingwangzhe/tags-cloud

先看源码

原项目 TagCloud 的核心算法其实非常优雅,值得保留。它由三个纯数学模块组成:

数学计算

好久都没做过纯数学题了,在Deepseek写代码的时候,顺便问一下这都是什么物理意义,计算能算,但逻辑需要思考很长时间…

1. 斐波那契球面分布

把 N 个标签均匀地散布在球面上,不是一件简单的事。如果直接按经纬度等距切分,极点附近的点会被挤压在一起。旧库用了一个巧妙的方案:

ϕ(i)=arccos⁡(−1+2i+1N)θ(i)=Nπ×ϕ(i)P(i)=(Rsin⁡ϕcos⁡θ, Rsin⁡ϕsin⁡θ, Rcos⁡ϕ)\begin{aligned} \phi(i) &= \arccos\left(-1 + \frac{2i+1}{N}\right) \\[4pt] \theta(i) &= \sqrt{N\pi} \times \phi(i) \\[4pt] P(i) &= \bigl(R\sin\phi\cos\theta,\ R\sin\phi\sin\theta,\ R\cos\phi\bigr) \end{aligned}

(2i+1)(2i+1) 的偏移确保没有任何点恰好落在球面极点。Nπ\sqrt{N\pi} 的黄金螺旋角让相邻点之间的经度差保持无理数比例,避免视觉上的对齐条纹。时间复杂度 O(N)O(N),100 个标签瞬间完成。

2. 旋转矩阵

交互体验用的是 Shoemake Arcball——一种基于四元数的 3D 旋转方案。用户拖拽时,屏幕坐标被投影到虚拟球面上,起点和终点之间构造一个四元数差量,然后叠加到当前旋转状态。相比欧拉角,四元数没有万向锁问题,旋转更流畅。

3. 透视投影

per=4R4R+zα=clamp⁡(per2−0.25, 0, 1)xscreen=cx+xrot×peryscreen=cy+yrot×per\begin{aligned} per &= \frac{4R}{4R + z} \\[4pt] \alpha &= \operatorname{clamp}(per^2 - 0.25,\ 0,\ 1) \\[4pt] x_{screen} &= c_x + x_{rot} \times per \\[4pt] y_{screen} &= c_y + y_{rot} \times per \end{aligned}

Z 轴越深(远离屏幕)→ perper 越小 → 标签缩小 + 变透明。per2−0.25per^2 - 0.25 的公式让远处的标签更快地淡出视野,避免球面背面的标签干扰视觉。近处的标签(per≈1per \approx 1α=0.75\alpha = 0.75,清晰可见。

渲染,但是我不想用 DOM

旧库的渲染方式是把每个标签做成一个 <span> 元素,每帧更新它的 transformopacity。100 个标签就是 100 个 DOM 节点在每一帧被重新布局——性能可想而知。

我的想法是:数学留在 CPU 里,渲染尽量走 Canvas(文本和图片)。需要交互的富媒体(SVG、视频、Web Components)保留 DOM overlay。Canvas 的 fillTextdrawImage 是像素级操作,不触发回流,60fps 毫无压力。

用 TS 改写

从 ES5 到 TypeScript 不只是加类型标注。整个架构被拆成了清晰的模块边界:

src/

├── core/

│ ├── distribution.ts // 斐波那契球面分布

│ ├── rotation.ts // 旋转变换

│ └── projection.ts // 透视投影

├── TagCloud.ts // 主引擎

└── index.ts // 导出入口

import { TagCloud } from "@xingwangzhe/tags-cloud";

const cloud = new TagCloud(document.getElementById("cloud"), {

tags: ["TypeScript", "Canvas", "3D", "Astro", "Bun"],

radius: 300,

spinY: 0.15, // Y 轴自旋速度(°/帧)

fontSize: 16,

color: "#ffffff",

onTagClick(item) {

if (typeof item === "string") {

window.location.href = `/tags/${item}/`;

}

},

});

// 运行时 API

cloud.setTags(["新的", "标签", "列表"]);

cloud.pause();

cloud.resume();

cloud.destroy();

类型系统让配置项一目了然。TagCloudOptions 的每一个字段都有 JSDoc,IDE 里悬停就能看到中英文说明。

多模态

这是新库最大的亮点——不再局限于纯文本tags 参数接受一个联合类型:

type TagItem =

| string // 纯文本 → Canvas 渲染

| { type: "image"; ... } // 图片 → Canvas 渲染

| { type: "svg"; ... } // SVG → DOM overlay 渲染

| { type: "html"; ... } // HTML → DOM overlay 渲染

| { type: "video"; ... } // 视频 → DOM overlay 渲染

| { type: "element"; ... } // 任意元素 → DOM overlay 渲染

渲染引擎自动分流:文本和图片走 Canvas 获得最佳性能;SVG、HTML、视频和 Web Components 走 DOM overlay 保持交互性和可访问性。

图片

new TagCloud(container, {

tags: [

{

type: "image",

src: "/avatar.webp",

width: 40,

height: 40,

onClick: () => open("/profile"),

},

"JavaScript",

"TypeScript",

],

radius: 300,

spinY: 0.15,

});

图片通过 CanvasRenderingContext2D.drawImage() 绘制,支持自定义宽高和点击回调。头像、Logo、图标都可以混在文字标签中间,在 3D 球面上一起旋转。

视频

new TagCloud(container, {

tags: [{ type: "video", src: "/demo.mp4", width: 120, height: 68 }, "前端", "全栈"],

radius: 350,

spinY: 0.1,

});

视频标签走 DOM overlay 渲染,autoplay muted loop playsinline 自动静音循环播放。点击视频标签会触发全屏——想象一下在标签云里漂浮着一段产品 Demo 的缩略视频。

Canvas 渲染

整个 Canvas 渲染器是内置的,但完全可替换。onRender 回调暴露了每帧的投影数据:

new TagCloud(container, {

tags: ["A", "B", "C"],

onRender(tags) {

// tags: TagData[] — 每帧的投影坐标

// { item, x, y, z, scale, alpha }[]

tags.forEach((t) => {

// 你可以用 Three.js、PixiJS 或任何方式绘制

});

},

});

如果不传 onRender,引擎会用内置的 Canvas 渲染器:自动创建 <canvas>、处理高 DPI 缩放、Z 排序后逐层绘制文本和图片。DOM overlay 也自动管理——创建、更新 transform、清理已移除的标签。

内置渲染器的细节:

  • Canvas 绘制文本和图片(高性能像素操作)
  • DOM overlay 渲染 SVG/HTML/Video/Element(保持交互性)
  • 每帧按 Z 深度排序(远处的先画),实现正确的遮挡关系
  • 点击检测用 raycast——遍历上一帧的 Canvas 标签坐标,找最近的命中

核心 API 一览

选项类型默认值说明
tagsTagItem[]标签列表
radiusnumber300球面半径 (px)
spinYnumber0Y 轴自旋速度,+右转 -左转
spinXnumber0X 轴自旋速度,+下转 -上转
reversebooleanfalse反转拖拽方向
inertiaDecaynumber0.96惯性衰减系数
dragSensitivitynumber3拖拽灵敏度
fontFamilystringsystem-ui字体
fontSizenumber14字号 (px)
colorstring#fff文字颜色
onTagClickfunction点击回调
onRenderfunction内置自定义渲染器

实例方法:setTags()pause()resume()destroy()

性能

指标数值
Bundle 大小~12KB (ESM) / ~3KB (gzipped)
零运行时依赖
100 标签 帧耗时< 5ms (旋转+投影+排序+渲染)
内存占用~6KB (100 个标签的浮点坐标)

数学计算全部是标量运算,没有矩阵乘法库依赖。每帧的浮点运算量:NN 个标签 ×\times (88 次乘法 ++ 44 次加法) 用于旋转矩阵变换 ++ 11 次除法用于透视投影 ++ O(Nlog⁡N)O(N \log N) 的 Z 排序。不碰 WebGL,纯 CPU 计算在 60fps 下完全够用。

Demo 在线

我的博客标签云实例 https://xingwangzhe.fun/tags/

标签云页面Demo: https://tagscloud.needhelp.icu/

npm 安装:

bun add @xingwangzhe/tags-cloud

# 或

npm install @xingwangzhe/tags-cloud

GitHub: https://github.com/xingwangzhe/tags-cloud

欢迎 Star 和 PR!