mcpField
B
build-interactive-particle-trail

build-interactive-particle-trail

github.com/mengto设计与 UI7.1k1.2k 次安装3 天前更新
代码仓库

关于 build-interactive-particle-trail

这项设计技能用于构建由离散微粒组成、可响应指针动作的粒子轨迹,适合需要粒子跟随手势的创作者;采用按移动距离发射和可循环使用的 GPU 粒子池。

主要功能

  1. 01按指针移动距离发射粒子
  2. 02沿手势路径插值布点
  3. 03循环使用固定 GPU 粒子池
  4. 04在顶点着色器中驱动粒子飞行
  5. 05复用粒子池实现按钮触发的爆发效果
  6. 06暂停动画并适配减少动态效果

适用场景

  1. 01快速移动指针时生成间距均匀的轨迹
  2. 02避免光标静止时粒子不断堆积
  3. 03通过按钮或键盘触发粒子爆发效果

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mengto/skills --skill build-interactive-particle-trail

安装后在 Claude Code 中输入/build-interactive-particle-trail确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
demo/
index.html
PROMPT.md

详细介绍

构建交互式粒子轨迹

每单位指针移动距离发射离散微粒,将它们均匀分布在整个线段上,并循环使用固定的 GPU 池。当粒子主要用于营造自主氛围时,使用 ambient-section-particles;当输出应为连续的流体或着色器带状效果时,使用 add-shader-cursor-trail。当单个颗粒必须对手势作出反应时,使用此 Skill。

摘录自 inner-green-3d.html:指针需要从程序化苔藓根部扬起花粉,同时避免快速扫过时留下空隙,也避免静止的光标不断堆出一团亮斑。

确定交互平面

将指针转换为标准化设备坐标,从摄像机发射射线,并与视觉表面附近的一个稳定平面相交。所有交互系统都复用该交点。

当指针离开或射线未命中时,重置上一个交点。否则,指针重新进入时会从过期的离开位置画出一条很长的对角线。

对于曲面可见表面,可以与实际碰撞体相交,或将响应平面放在其正前方。不要对每片装饰性叶片或每个粒子都进行射线投射。

按距离发射

间距才是关键,而不是时间:

const distance = current.distanceTo(previous);
const count = Math.min(14, Math.floor(distance / 7));

for (let i = 1; i <= count; i++) {
  point.lerpVectors(previous, current, i / count);
  spawn(point);
}

if (count) {
  previous.copy(current);
  idle = 0;
} else if ((idle += dt) > 0.055) {
  spawn(current);       // a resting hand trickles; it does not pump
  idle = 0;
}

定时发射器会在快速划动后留下点状间隙,并在静止的手下堆积粒子。线段插值可以同时解决这两个问题。将每帧的发射数限制为 14,避免标签页恢复或指针瞬移时一次耗尽粒子池。

采用以下实用默认值:

参数默认值可避免的问题
路径间距7 px/世界单位间距过大会像采样点,过小则会变成带状效果
每帧上限14 个微粒瞬移会覆盖整个粒子池
空闲间隔55 ms空闲时不发射会显得呆板;间隔更短则会形成热点
粒子池大小620足以形成 1.6 s 的重叠轨迹,同时避免无界分配
生命周期1.6 s轨迹过短会断裂;过长则会遮蔽内容
随机尺寸0.50–1.15点的大小相同会像点描画笔
DPR 上限2高密度显示器上的填充成本会激增

循环使用 GPU 粒子池

一次性为原点、速度、出生时间和随机种子分配类型化数组。生成粒子时推进环形队列头:

const index = head;
head = (head + 1) % POOL_SIZE;

origins.set([x, y, z], index * 3);
velocities.set([vx, vy, vz], index * 3);
birth[index] = elapsed;
random.set([size, phase], index * 2);
dirty = true;

仅在粒子重新生成后将动态属性标记为需要上传。在顶点着色器中根据 age = uTime - aBirth 计算飞行效果;不要在 CPU 上积分 620 个粒子:

float age = uTime - aBirth;
float u = age / uLife;
vec3 p = position + aVelocity * age * (1.0 - 0.34 * u)
       + vec3(sin(aRandom.y * 6.283 + age * 2.6) * 22.0 * u,
              46.0 * age,
              0.0);
vAlpha = smoothstep(0.0, 0.09, u)
       * (1.0 - smoothstep(0.40, 1.0, u));

在顶点着色器中将大小设为零并移出裁剪空间,以剔除已消亡的点。将粒子池渲染为一次 THREE.Points 绘制调用,使用预先构建的柔和径向纹理、加法混合以及 depthWrite: false。

将粒子池用于爆发效果

将按钮或获得焦点的控件映射到同一个交互平面,并调用 spawn 40–60 次,扩散范围设为 2.5×。复用一个粒子池,统一视觉风格、预算和清理流程。

提供一个真正的 释放花粉 按钮,显示清晰的焦点样式,并提供 aria-live 确认反馈。不要让指针移动成为查看或触发效果的唯一方式。

尊重动态效果偏好和生命周期

  • 在 prefers-reduced-motion: reduce 下,不要推进粒子年龄。预先生成一条设计好的弯曲轨迹,并让爆发按钮将其替换为更密集的静态构图。
  • 将 dt 限制为 1/30 s,并在恢复时重置时间。
  • 在 document.hidden 或该区段位于视口之外时暂停。
  • 使用 ResizeObserver 调整尺寸,处理视口尺寸为零的情况,并将 DPR 上限设为 2。
  • 使用 Pointer Events,以便在表面支持时让触控笔和触摸输入也能参与;避免仅提供悬停操作说明。
  • 拆除时,取消动画帧、断开观察器、移除事件监听器,并释放点几何体、材质、精灵纹理和渲染器。

如实说明成本

每个粒子的飞行计算并非主要成本;加法混合的点过度绘制和属性上传才是。保持精灵较小、粒子池固定,仅在环形队列变化时上传,并优先缩短生命周期,而不是扩大粒子池。在宣称着色器方案更快之前,先在高密度显示器上测量 GPU 帧时间和上传成本。

验证

  • 沿同一路径分别缓慢移动和快速轻扫;间距应大致相等。
  • 停止移动;确认只出现零星粒子,而不是不断增大的不透明团块。
  • 离开后重新进入;确认不会从过时的位置形成对角线桥接。
  • 触发键盘爆发效果,确认它复用同一个对象池。
  • 测试连续触发多次爆发;对象池长度保持为 620,内存占用保持稳定。
  • 测试 390×844、1440×900、触摸/触控笔和快速调整窗口大小。
  • 测试 ?reduced=1:组合效果保持静止、不老化,爆发按钮仍可用。
  • 隐藏/显示并离开后重新进入视口;确认时间不会跳跃。
  • 使用 Tab 键遍历控件,确认焦点可见且有实时播报。
  • 确认两种尺寸下控制台均无错误。

使用 demo/index.html 作为实际验证示例,并使用 demo/PROMPT.md 重新创建或改编它。

评论

支持 Markdown