零依赖的 Canvas2D 动效:同心环在噪声场里缓慢摆动,像年轮 / 指纹。
复刻自 Vanta 的 TRUNK 效果(原效果作者 Kjetil Midtgarden Golid)。
Vanta 的 vanta.trunk.min.js 本身只有 10KB,但它要求你先引入 800KB 的 p5.js——而 p5 在这里只用到 noise() 和 random() 两个函数。这个仓库把用到的那部分移植了过来,加起来 12KB,单文件,无任何依赖。
index.html 打开就是一个可调参的演示页:拖动滑块即时生效,地址栏同步更新 —— 把链接发给别人,看到的就是同一张图。
# 直接双击 index.html 也行(无依赖、无构建、可离线)
open index.html参数全部走 URL,方便分享和固化:
?color=458498&chaos=5&spacing=0&rings=55&speed=0.3&seed=7
| 参数 | 默认 | 说明 |
|---|---|---|
color |
458498 |
环的颜色(六位十六进制,不带 #) |
chaos |
5 |
摆动幅度。Vanta 原版默认是 1,越大越乱 |
spacing |
0 |
环间距额外增量,>0 时环与环之间会拉得更开 |
rings |
55 |
环数。原版桌面用 55、移动端用 35;嫌卡就调小 |
speed |
0.3 |
动画速度倍率 |
seed |
7 |
随机种子。原版每次刷新都不一样的图案,给个固定值就能复现 |
复制 index.html 里 <script> 中画布部分的代码即可,没有任何依赖。核心就是每帧:
for (let i = 0; i < rings; i++) {
const dim = 0.12 * i + 0.2; // 噪声取样维度随环递增
const base = 4 * i + 50 + (i * spacing || 0); // 基础半径
ctx.beginPath();
for (let a = 0; a < 360; a++) {
const ca = cosA[a], sa = sinA[a]; // 预算好的 cos/sin 表
const r = chaos * 20 * noise(ox + ca * dim, oy + sa * dim, oz + dim) + base;
a ? ctx.lineTo(r * ca, r * sa) : ctx.moveTo(r * ca, r * sa);
}
ctx.closePath();
ctx.stroke();
}
// oy -= 0.02 * speed; oz += 0.00005 * speed; ← 每帧推进TrunkField.tsx 是现成组件("use client",fixed 全屏 canvas,自带 resize 与卸载清理),顶部几个常量就是上面那张参数表:
import TrunkField from "./TrunkField";
<div style={{ position: "relative", height: "100vh" }}>
<TrunkField zIndex={0} />
</div>每帧要算 rings × 360 = 19800 次 noise(每次 4 个八度)。实测在一台 2015 款 Intel MacBook Pro 上约占单核 20–30ms/帧。
- 想更省:把
rings降到 35(原版移动端的取值),直接省 36% - 想更省且不介意边缘略糊:canvas 按 CSS 像素渲染、不乘
devicePixelRatio
- 速度是帧驱动的,照抄原版:每帧固定推进
oy -= 0.02。所以同一个speed在 120Hz 屏上会比 60Hz 快一倍。要跨机器一致,得改成按时间推进(oy -= 0.02 * speed * elapsed / (1000/60))。 - 画到第 360 个点就
closePath(),最后一段是直连的弦——和原版(p5 的endShape(CLOSE))行为一致,角度只差 1°,看不出来。
noise() / random() 是从 p5.js 1.1.9 逐行移植的,做了对拍验证:
- 同一颗种子下,400 个采样点的噪声值 最大差 0.000e+0(逐位一致)
- 把两边随机种子钉死后同帧比较:实心像素 100% 颜色一致,笔画位置对齐;残差只出现在 1px 细线的抗锯齿边缘
这个仓库是复刻,不是原创效果的诞生地:
- 原效果:Kjetil Midtgarden Golid (@kgolid) — p5ycho/trunk
- 本仓库复刻的是 Vanta 收录的
TRUNK版本 — MIT, © 2020 Teng Bao noise()/random()移植自 p5.js 1.1.9(LGPL-2.1)——只移植了这两个函数用到的部分
本仓库代码以 MIT 发布,见 LICENSE。
