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

推荐订阅源

GbyAI
GbyAI
B
Blog
Stack Overflow Blog
Stack Overflow Blog
量子位
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
腾讯CDC
D
DataBreaches.Net
Microsoft Azure Blog
Microsoft Azure Blog
G
Google Developers Blog
M
MIT News - Artificial intelligence
P
Proofpoint News Feed
罗磊的独立博客
L
LangChain Blog
V
Visual Studio Blog
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
宝玉的分享
宝玉的分享

BlogFinder

日常漫步 Vol.24 之漫步前山河 - 雅余 周报 #1-聊聊本周的收获 - Edwin's Blog 我的OpenCode必装插件与Skill Write Something 掌中之物未必在掌握之中 · CRIVU PiliNara,一个更顺手的 PiliPlus 分支 「NekoEcho」:做一个必有回响的猫娘主题博客 2026-05 书影音总结 简化博客主题 - 安迪 我第一次发布 npm 包 拾花小记#45:中考前的二三事 – 小改学习志 黛西花园5月游 #18 枇杷又熟了的五月月报 一些奇奇怪怪的需求?word仿方正书版的几个小操作 - Xiobb's Blog 0419 御温泉之旅 修复了一些bug,网站基本上趋于稳定了 - 新锐博客 又回到四十年前 如何定义成功 迷鹿屋2026已重新上线 科技冰火两重天+一周回顾 ${title} 热度退了,我反而用得更深了-咕咚同学 我到底该不该换个域名? 随身WIFI折腾记 - 安迪 博客撰写体验提升——hexo pro插件 为什么不用相机把屏幕上的接关密码拍下来? 国清寺与天台山 – Ouroboros ★★★★☆《挽救计划》——久违的经济上行感 - Davidの3号基地 删除右键“打开方式”里多余选项 第三周刊_No.53|一切都会被支付两次
d3-force-3d 构建时预计算 3D 网络布局,减少客户端渲染压力 ...
作者:xingwangzhe · 2026-06-28 · via BlogFinder

🕒 阅读时间: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/