mcpField
F
fire-paper-loader

fire-paper-loader

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

关于 fire-paper-loader

使用 Vanilla JavaScript、WebGL 和 Worker 构建的燃烧纸环加载动画技能,面向页面构建期间主线程可能阻塞的场景。它让火焰在 OffscreenCanvas 上持续绘制,并涵盖预热、进度衔接、淡出和无障碍状态。

主要功能

  1. 01Worker 与 OffscreenCanvas 绘制
  2. 02主线程阻塞时保持动画
  3. 03并行编译并预热着色器
  4. 04加载进度平滑衔接页面动画
  5. 05减少动态效果与进度状态支持

适用场景

  1. 01为加载时间较长的页面制作加载动画
  2. 02避免页面构建阻塞时动画停顿
  3. 03将加载进度衔接到页面开场动画

安装

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

$npx skills add https://github.com/mengto/skills --skill fire-paper-loader

安装后在 Claude Code 中输入/fire-paper-loader确认已加载。

包含的文件

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

demo.html
PROMPT.md
agents/
openai.yaml

详细介绍

火焰纸环加载器

页面加载时,黑色背景上燃起一圈纸火。其实现机制是:火焰由 Worker 在 OffscreenCanvas 上绘制,并从 <head> 启动,因此即使主线程阻塞数百毫秒,火焰也会持续燃烧。 火焰效果只是表象;无论外观如何,加载器停顿 450 ms 都会让人感觉页面卡住了。

最相关的技能:web-design-performance 涵盖通用页面速度优化;本技能专门讲解如何让加载器在主线程阻塞时继续运行。如果需要滚动过渡时的燃纸效果(不是加载器),其火焰 GLSL 也是 kbFlame,但由页面绘制。如果需要旋转指示器或 AI 状态光球,请使用 thinking-orbs。

根据源码验证的技术栈:原生 JavaScript、原始 WebGL 1(GLSL ES 1.00,加载器中不使用 Three.js)、Worker 加 OffscreenCanvas,以及用于标签和淡出的 CSS。

从 head 开始,而不是从 body 开始

症状:加载器晚 1–2 s 才出现;Worker 始终无法获取 GL 上下文;或者在 three.js 下载期间火焰静止不动。只有主线程空闲时,Worker 才能启动并创建 WebGL 上下文;此时机是在 <head> 中加载样式表期间,之后要等到页面解析完成才会再次出现。

  • 在 <head> 中的 <script> 里:创建 canvas,调用 transferControlToOffscreen(),从一个小型引导脚本的 Blob URL 启动 Worker,然后发送 {kbPre, canvas},并立即在 Worker 中调用 getContext('webgl', {antialias:false, alpha:false, depth:false, stencil:false})。将 canvas 和 Worker 保存在 window.__kbPre 上。
  • 引导脚本只是一个外壳。加载器自身的 <script>(位于 body 后部)会将其 textContent 通过 {kbInit, src, canvas:null, t0, origin: performance.timeOrigin, reduced, view, target} 发送给 Worker,由 Worker 对其执行 eval。同一段源码在两个线程上运行:const IN_W = typeof document === 'undefined'。使用两份代码会导致它们逐渐不一致。
  • 对每一步都做好保护:如果没有 Worker、OffscreenCanvas 或 transferControlToOffscreen,Worker 触发 onerror,或者收到 fail 消息,就使用相同代码在主线程上绘制,并执行相同的预热流程。
  • 转换时钟。Worker 的 performance.now() 与页面不同:发送 performance.timeOrigin,并在 Worker 中加上 performance.timeOrigin - origin,这样页面读取的每个时间戳都属于页面自己的时钟。

并行编译、预热流水线,然后显示

症状:最初的可见帧是整个加载过程中最慢的(30–130 ms,在 Metal 上有一次达到 408 ms)。

  1. 创建两个程序(ring、cinders),附加着色器,为两个程序都调用 linkProgram,然后再查询状态。使用 KHR_parallel_shader_compile 时,驱动会在自己的线程中编译;逐个着色器读取 COMPILE_STATUS 会抵消这一优势。存在该扩展时,跳过逐个着色器检查。
  2. warmUp():在 1x1 视口中运行一帧真实绘制,同时绘制两个程序,然后等待 readPixels 读取 1 个像素。首次绘制会构建 GPU 流水线;要在任何内容显示之前完成这一步。
  3. 然后才启动 requestAnimationFrame。页面的 LOAD.ready() 会在 Worker 完成绘制后解析(通常需要 0.15–0.3 s,超时时间为 1.2 s);构建流程会等待它,这样主线程就不会被占用一秒,火焰也不会静止不动。
  4. 只显示一个 canvas,不要显示两个。在首批可见帧中让两个线程都绘制火焰,最初会耗费编译时间。

进度环的节奏,应按视觉效果来规划

症状:煤块的大小停滞一秒,然后猛增 2.5 倍;或者,一个停滞的环被更快的环追上,看起来像两团火。

  • 在 S = gain + ln(radius / first radius) 中规划增长,而不是直接按原始进度 u 规划。S 中的等幅步进在任何尺寸下看起来都像是等幅增长。淡入在 S 中占 0.3(当进度为 1 时,尺寸被压住了 0.9 秒)。
  • 用临界阻尼弹簧让 S 跟随目标值(角频率 5/s,速度限制为 1.6/s,以 1/120 s 为子步长积分)。目标值是真实进度,但绝不会落后于缓慢增长值:creep = 0.38*(1 - exp(-t/800 ms));等待期间,目标值不能超过 WAIT 0.290,并以 2.5 s 的时间常数平滑接近 CEIL 0.335。硬性停顿和成块到来的进度会让圆环看起来像是先停住,再突然被推了一把。
  • 圆环绝不静止:最小速度 VMIN 为 0.016/s(在 8 fps 下约为每帧 5 px),最大速度 VMAX 为 0.05/s。视觉半径:进度为 0 时是 4 px 的小光点,到 0.15 时增至 5.8 px,之后使用火焰自身的半径。
  • 页面尚未显示任何内容前,进度会按自己的时钟缓慢增长,因此即使 three.js 仍在加载,圆环也会持续运转。

无停顿地交接

症状:圆环猛地跳入页面的开场动画;或者加载器消失时,页面卡顿 55-240 ms。

  • done() 会在 S 空间中运行一段 Hermite 曲线,将速度从缓慢增长值平滑过渡到开场动画的速度 V_HAND 0.24(u/s),持续 ds = clamp(2.8*du/vH, 0.30, 1.2) 秒,并在圆环边缘到达交接进度 HAND 0.36 时发出通知。页面会从该确切进度和时钟继续自己的开场动画。如果 rAF 停滞,安全计时器(doneDur + 400 ms,页面端为 2.4 s)仍会解除等待。
  • 标签会到达 100,并在合成器上淡出(transition: opacity .40s linear .12s):繁忙的首帧不会让它过早消失。
  • close():设置 alive = false,并通知 worker 停止绘制。淡出期间,两个上下文同时绘制会争用 GPU,使淡出动画的每帧耗时达到 35-67 ms。最后一帧会继续显示,并通过 CSS opacity 淡出(常规情况下为 120 ms,减少动态效果时为 250 ms);淡出过程使用 Web Animations 关键帧,沿着开场动画自身的曲线缩放,让圆环在页面上的边缘位置保持不变。
  • 页面仍在屏幕上时,不要调用 worker.terminate()。 它会在任意时刻阻塞主线程 55-240 ms。发送 free(在 worker 线程中释放 GL 上下文),并且只在 visibilitychange 变为 hidden 或 pagehide 时终止 worker。也不要在主线程调用 loseContext():它会等待所有排队的 GPU 工作完成,包括开场动画的首帧(100 ms)。
  • 在各构建阶段之间主动让出执行权:await LOAD.step(p) 会设置进度,并且最多每隔 45 ms(worker 绘制时为 90 ms,因为它只需要更新计数)等待一次 requestAnimationFrame,再等待 setTimeout 0。在隐藏标签页中不会等待,因为 rAF 不会运行。

燃烧效果:页脚效果是参照

所有者按顺序提出的症状:“太静态了”“黑色部分应该使用 Alpha 遮罩”,然后在尝试了四级海报化渐变后又说:“太不真实了,还是之前的效果更好,页脚的燃烧效果更好”。

  • 发光渐变,而非海报化阶梯。 余烬区域从黄色平滑过渡到橙色,再到深红色;高温区将黄色向后延伸,最后以清晰、撕裂状的边缘收尾。生硬的阶梯看起来像海报。
  • 前沿有一条明亮的珠状细线,宽 0.7-5 px,沿线粗细收缩变化(均匀的线条看起来像霓虹灯),并带有紧凑的 2-5 px 加法辉光。不能更宽:过大的辉光会让边缘变成霓虹光环。
  • 它必须动起来。 热脉冲沿边缘双向传播(run),局部区域以 7 Hz 闪烁(flk),珠状光点漂移,翻卷的热浪向后掠过区域。静帧看起来没问题;加载器的效果要看动态表现。
  • 金色,而非奶油色。 最亮核心颜色为 (1.85, 1.02, 0.30),余烬颜色最高为 (1.10, 0.64, 0.17),且只出现在高温区域。奶油色和卡其色是因为亮部变白或通过调暗整体来降低亮度;较暗的区域应沿渐变过渡至深红色。
  • 多孔炭化层,不要描边。 余烬后方的炭化带接近黑色 (0.0085, 0.0062, 0.0048),带有纤维纹理;孔洞会随时间形成,并逐渐露出下方的画面。绝不要用发光边缘勾勒孔洞:它们会连成沿边缘延伸的一整条轮廓。
  • 圆环绝不能从圆盘或字母“o”开始。 它从一段撕裂状的余烬弧开始,厚 3.5-7 px,略多于半圆,两端沿圆周爬行并在 6.5 到 12 px 之间相接。圆环宽度绝不能低于 3.5-7 px,否则看起来像描出的轮廓。
  • 余烬飞屑:四组实例(1800、720、360、160 个四边形,使用种子为 0x2f6b9d1 的 mulberry32),仅从上方弧段(20-160 度)飞出,存续时间短,颜色从炽热橙色到深红色,绝不变白。先使用 ACES 色调映射,再在着色器中转换为 sRGB(aces 然后 srgb),因此将 gl_FragColor 的 Alpha 设为 1,并将画布设为 alpha:false。

实测开销

  • 在主线程上绘制时,如果构建过程会一次阻塞主线程 250-450 ms,火焰帧之间最长间隔为 450 ms。在同一页面的 Worker 中则为 9 ms。
  • 渲染 DPR 最高为 1.5(此处源码的上限;Web 通用上限为 2)。圆环由一个全屏三角形和 3040 个余烬四边形组成;开销来自片元着色器,而非余烬飞屑。
  • 时间步进:Worker 按自身帧绘制;显示时钟属于页面,因此动画的 dt 会按设计进行限制(dtp 最大为 1.5 s,弹簧子步进为 1/120 s)。

生命周期与无障碍

  • 标签上使用 role="progressbar",并保持 aria-valuenow 同步;画布设为 aria-hidden。计数以文本形式呈现,因此即使没有火焰效果也能读取。
  • prefers-reduced-motion: reduce:不推进时间(tSec = 0),不显示余烬飞屑,不进行合成器缩放;圆环仍会随进度展开,呈现静止且带纹理的边缘,淡出时长为 250 ms。画面按静态效果设计,而不是隐藏起来。
  • 标签页隐藏时:两个线程中的 rAF 都会暂停;step() 停止等待;done() 会立即完成,且只在此时终止 Worker。
  • 进度低于 40% 且持续 12 s 后,标签会显示“仍在引燃”:应告知用户连接较慢,而不是让加载过程悄无声息地无限持续。
  • 调整尺寸:向 Worker 发送新的 view(w、h、DPR);画布会在下一帧调整尺寸。

此技能的适用范围

保留机制:提前启动 Worker、同一份源码运行于两个线程、并行编译并等待 1 px 预热、S 空间节奏控制、Hermite 交接、先停止再用 CSS 淡出、不提前终止。可自由替换舞台呈现:火焰的调色板和形状、标签文案,以及它交接到的页面。演示将页面的开场动画作为替代:done() 之后,加载器的圆环会沿开场曲线继续运行 1.8 s,直到离开画面;此时真实页面会根据交接进度绘制自己的边缘。

来源:摘自 KIBORI 木彫 的 KINDLING 加载器。该页面使用滚动联动的 Three.js 木工展示,1.1 MB 的构建产物会让主线程最多阻塞 1.2 s。

评论

支持 Markdown