零依赖 canvas 动画:bg-fx 的辉光、音画联动与性能预算
零依赖 canvas 动画:bg-fx 工程笔记
hero 首屏的背景动画(bg-fx)是 v2 的主实验:六种模式(星尘/扫描/雨/
波形/网格/关闭)共用一套引擎。全程零依赖,只有 astro 一个包。
质感从哪来
平涂小像素是“简陋感”的来源。电子感三件套:
- 辉光(additive):深色主题下把光晕层用
globalCompositeOperation = 'lighter'叠加(多次绘制自然提亮),核心层回 source-over;浅色主题 退回普通叠加防洗白(背景亮度算一次,主题切换时重算)。 - 分层:星尘分前景(accent)/背景(light);流星带渐隐尾迹; WAVE 模式三层(辉光 → 波形 → 镜像倒影)。
- 脉冲:全局呼吸(
0.75 + 0.25·sin(t))让亮度活起来。
音画联动:事件解耦
src/lib/sfx.ts 的 playClick / playHover / playNav 在合成音效的同时向
window 派发 CustomEvent('8bit:fx'),bg-fx 监听后生成对应光效
(click→扩散波纹 / nav→全屏扫光 / hover→星火)。声音与画面零耦合,
将来加新的音效或新的光效都不用改对方。
性能预算(静态站的底线)
devicePixelRatio封顶 1.5- 粒子数按 canvas 面积裁剪(
clamp(area/9000, 70, 170)) - hero 滚出视口(IntersectionObserver)和标签页隐藏时停帧
- 样式串预生成,避免逐帧拼
rgba()字符串 - 坐标全部整数化(
fillRect),天然防锯齿
踩过的两个坑
坑一:启动死锁。 启动门 wantsRun() 里包含了 running 标志,
而 running 只能由 sync() 开启——动画永远无法启动(用户看到的是
完全静止)。修复:门禁只看外部条件(视口可见 + 页面可见),
不自引用自身状态。
坑二:reduced-motion 冻结。 尊重 prefers-reduced-motion 的初版
实现是“渲染一帧静态画面”。对装饰性背景这是过度执行——用户要的就是
活力。改为:降频半速(每 2 帧推进一步)而不是冻结,保留克制感同时
不牺牲体验。
小结
零依赖不是“抠门”,是让 AI 维护和性能预算都变简单的架构选择: canvas 2D 能做出 neon 质感,就永远不需要引入渲染库。