关于 build-interactive-particle-trail
这项设计技能用于构建由离散微粒组成、可响应指针动作的粒子轨迹,适合需要粒子跟随手势的创作者;采用按移动距离发射和可循环使用的 GPU 粒子池。
主要功能
- 01按指针移动距离发射粒子
- 02沿手势路径插值布点
- 03循环使用固定 GPU 粒子池
- 04在顶点着色器中驱动粒子飞行
- 05复用粒子池实现按钮触发的爆发效果
- 06暂停动画并适配减少动态效果
适用场景
- 01快速移动指针时生成间距均匀的轨迹
- 02避免光标静止时粒子不断堆积
- 03通过按钮或键盘触发粒子爆发效果
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mengto/ skills --skill build-interactive-particle-trail安装后在 Claude Code 中输入/build-interactive-particle-trail确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamldemo/index.htmlPROMPT.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 重新创建或改编它。