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

推荐订阅源

Latest news
Latest news
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
L
LINUX DO - 热门话题
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
V
V2EX
博客园 - 司徒正美
B
Blog RSS Feed
AWS News Blog
AWS News Blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
Last Week in AI
Last Week in AI
NISL@THU
NISL@THU
博客园 - Franky
P
Proofpoint News Feed
博客园_首页
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
S
Schneier on Security
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
月光博客
月光博客
WordPress大学
WordPress大学
The Hacker News
The Hacker News
T
Threatpost
阮一峰的网络日志
阮一峰的网络日志
A
Arctic Wolf
Microsoft Azure Blog
Microsoft Azure Blog
T
The Exploit Database - CXSecurity.com
Engineering at Meta
Engineering at Meta
罗磊的独立博客
T
The Blog of Author Tim Ferriss
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
K
Kaspersky official blog
SecWiki News
SecWiki News
云风的 BLOG
云风的 BLOG
美团技术团队
C
Cybersecurity and Infrastructure Security Agency CISA
博客园 - 【当耐特】
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog
S
Security Affairs

姓王者的博客

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贪吃蛇贡献图 | 姓王者的博客
d3-force-3d 构建时预计算 3D 网络布局,减少客户端渲染压力 | 姓王者的博客
作者:xingwangzhe · 2026-06-28 · via 姓王者的博客

🕒 阅读时间:4 分钟📝 字数:1237👀 阅读量:Loading...

前言

友链图谱 是一个汇聚了 1600+ 节点2200+ 连接的 3D 友链网络可视化项目。数据源是 links/*.yml 文件,渲染层基于 3d-force-graph(Three.js + d3-force-3d)。

上一篇文章中,我介绍了友链图谱从 2D 升级到 3D 的整体架构——包括 3D 力导布局的数学原理、节点渲染和高亮系统。但那时的方案是:构建时只输出数据,客户端自行跑力导仿真

这个方案有两个痛点:

痛点 表现
加载慢 每次刷新都要从 [-5,5] 随机位置重新跑动画
hover 卡顿 每次悬停触发 Graph.refresh() → 遍历 1600 节点计算颜色
构建浪费 构建 2 秒完成,但客户端要跑 5-10 秒动画

声明:本文在撰写过程中使用了 AI 工具辅助分析技术资料、整理踩坑经验及润色排版。

思路:构建时预计算位置

「既然 d3-force-3d 在客户端跑,那在构建时(Node.js)也跑一遍不就提前拿到位置了吗?」

d3-force-3d 官方文档明确支持这种做法:

simulation.tick() — "This method can be used in conjunction with simulation.stop to compute a static force layout."

于是我在 Astro API 端点 graph.json.ts 中加入了构建时预计算:

import { forceSimulation, forceLink, forceManyBody, forceCenter } from "d3-force-3d";

const sim = forceSimulation(nodes, 3) // 明确 3 维!

.force(

"link",

forceLink(links)

.id((d) => d.id)

.distance(30),

)

.force("charge", forceManyBody().strength(-60))

.force("center", forceCenter(0, 0, 0)) // 默认 strength=1

.alphaDecay(0.02)

.velocityDecay(0.3);

for (let i = 0; i < 300; i++) sim.tick();

sim.stop();

客户端改成加载预计算位置,直接冻结:

.cooldownTicks(0)

.cooldownTime(0)

然而……事情没那么简单。

踩坑 1:平面坍塌

跑出来的效果:

X: std=635 范围 [-1780, 1594]

Y: std=523 范围 [-2107, 1317]

Z: std=141 范围 [-200, 200] ← 被压扁了!

3D ratio = 0.223 ⚠️ 扁平

Z 轴被压到只有 ±200,不管怎么调参数——distance=30 还是 40charge=-60 还是 -120alphaDecay=0.02 还是 0.005——Z 永远塌在初始半径以内。

原因forceCenter(0,0,0) 的默认强度是 1.0,而 charge 只有 -60link 只有 distance=30。center 力比其他力大了两个数量级,强行把所有节点往原点拉,Z 轴首当其冲被消解。

有趣的是,客户端渲染时同样的参数看起来却是"3D 网络"——因为客户端跑到 200 tick 就停了(cooldownTicks),还没收敛到平面,停在了一个好看的暂态。但构建时要的是一劳永逸,不能依赖"没跑完"。

踩坑 2:花哨尝试一一失败

方案 结果
forceRadial(800).strength(0.05) 强制球壳,不是网络
自定义 zBias 力(随机 Z 扰动) 被 alpha 衰减消解,Z 还是塌
去掉 forceCenter,只用 forceX/forceY 中心节点子节点形成圆盘
forceCenter.strength(0.3) 0.651,不够

每次调整都离目标差一点,走了很多弯路。

最终方案:三轴等强 + 弱居中

关键洞察是:不要对抗 forceCenter,而是把它减弱到和其他力一个量级

const sim = forceSimulation(nodes, 3)

.force(

"link",

forceLink(links)

.id((d) => d.id)

.distance(40),

)

.force("charge", forceManyBody().strength(-120))

.force("center", forceCenter(0, 0, 0).strength(0.02)) // ← 关键!

.alphaDecay(0.01)

.velocityDecay(0.4);

for (let i = 0; i < 500; i++) sim.tick();

sim.stop();

参数 作用
link.distance 40 连接节点间距
charge -120 排斥力(比默认强一倍)
center.strength 0.02 极弱居中,仅防漂移
alphaDecay 0.01 慢冷却,充分收敛
velocityDecay 0.4 与 3d-force-graph 默认对齐
tick 500 完全跑完

为什么 0.02 有效?

strength=0.02 意味着每 tick 所有节点只向中心移动 2% 的距离。这刚好抵消整体漂移,但完全不足以压倒 link 和 charge 力。三种力在同一个量级上互相平衡,自然形成 3D 散布。

结果:

X: std=478 范围 [-1288, 1265]

Y: std=312 范围 [-640, 1315]

Z: std=357 范围 [-1076, 802]

3D ratio = 0.747 ✅ 良好的 3D 网络

三轴散布均衡,没有平面、没有球壳、没有圆盘,是一个真正的体积网络(volumetric network)。

客户端配置

客户端直接加载预计算位置,不再跑额外仿真:

.graphData(graphData)

.warmupTicks(0)

.cooldownTicks(0)

.cooldownTime(0)

.d3AlphaDecay(0.02)

.d3VelocityDecay(0.3);

注意 linkcharge不能禁用d3Force(null)),否则 3d-force-graph 的连线渲染会丢失。只要 cooldownTicks(0),仿真在一 tick 后立即冻结,位置不会被挪动。

效果对比

指标 客户端渲染 构建时预计算
页面加载 等 5-10 秒动画 即开即用
构建时间 2 秒 20 秒(一次编译,永久使用)
hover 卡顿 有(Graph.refresh) 无(已优化)
3D 散布 暂态,依赖 timing 稳定
布局可复现 ❌ 每次随机 ✅ 固定(只要 seed 不变)

总结

构建时预计算 3D 力导布局完全可行,关键不是"要不要用 forceCenter",而是把 forceCenter 的强度降到和其他力一个量级

center.strength 结果
1.0 平面(center 主宰)
0.0 漂移(无约束)
0.02 3D 网络 ✅(三种力平衡)

项目仓库:xingwangzhe/FriendLinks — 欢迎 star 和 PR!

友链图谱在线体验:https://links.needhelp.icu/