发布日期:2026-08-31
标签:前端 / React / Canvas / WebGL / 组件库 / cos-design / 视觉特效 / 交互设计
做活动页、登录屏、品牌 Landing 时,背景常常是个尴尬的存在:纯 CSS 渐变太「模板站」,Three.js 流体 Demo 又太重,临时抄一段 CodePen 往往只能撑一次活动。
我在 cos-design 里一直在补这一层------可装包、可调参、能交互的背景动效。v3.6 做了深海气泡场,v3.5 做了水面涟漪和烟雾雾气,v3.7 把图片预览做成了 13 种「能摸」的交互。
v3.8.0 这次一口气上了 5 个重磅背景组件,组件总数来到 91 。它们气质完全不同,但共享同一套工程约定:fill 铺满、bindVisibilityPause 切 tab 暂停、prefers-reduced-motion 静态帧降级、独立 @cos-design/* 分包。
全文约 12 分钟 。建议先打开 Playground(侧栏 → 背景动效),边读边摸------每个组件我都按侧栏顺序写:先讲为什么要做,再拆核心实现,最后留关键交互帧给你对照。
仓库 :github.com/jiaxiantao/... · 版本 :cos-design@3.8.0
| 组件 | 中文 | 渲染路线 | 独立包 |
|---|---|---|---|
SoapBubbles |
肥皂泡天空 | Canvas 2D 薄膜光学 + 融合 | @cos-design/soap-bubbles |
DandelionField |
蒲公英播种 | Canvas 2D 生命周期 + 物理 | @cos-design/dandelion-field |
LavaBubble |
熔岩泡 | CPU 场仿真 + WebGL 着色 | @cos-design/lava-bubble |
InkBloom |
墨染清水 | Canvas 2D 密度/速度场 | @cos-design/ink-bloom |
AuroraVeil |
极光帷幕 | Canvas 2D 光带轮廓 | @cos-design/aurora-veil |
开场:五个场景,五种「介质」
下面五张图,就是 Playground 里五个新组件的默认态------从肥皂泡到极光,一张比一张「不像 CSS 能做出来的」:





这五个组件不是「五种配色方案」,而是五种介质模型:
- 肥皂泡 --- 薄膜干涉色 + 上升气流 + 融合/爆裂
- 蒲公英 --- 植株生命周期 + 种子飘飞 + 落地发芽闭环
- 熔岩 --- 壳层鼓起、闷裂、空腔回填的高度场
- 墨染 --- 浓墨滴入清水后的密度场流体
- 极光 --- 垂向光带在星夜里的弯曲与脉冲
对外都是几个 props;对内各自拆模块。接下来五个章节,我会按这个顺序往下走------每节末尾留 Playground 链接,方便你对照动效。
一、SoapBubbles:薄膜光学,不是几个半透明圆
v3.8.0 的第一个背景,我从最「轻」的开始:纯 Canvas 2D,没有场仿真,但要骗过眼睛------泡膜得泛虹彩,碰在一起得会融,戳破得溅水珠。

它解决什么问题?
「肥皂泡背景」最容易翻车:画几个 arc + 白高光,看起来像玻璃球。真实肥皂泡的关键是薄膜干涉 ------厚度变化带来彩虹色斑,以及融合时 neck 收缩的 metaball 形态。
核心实现:film + merge + pop 三件套
薄膜色 film.ts:filmRgb(thickness) 在 8 个干涉色 stop 间插值;drawSoapIridescence 在泡内撒 7~12 个 softBlob 色斑 + rim 色带,按 lambertAtAngle 受光。
运动 :每个泡有 rise(上升力)、gust(阵风倍率)、drift(横向目标),用指数 ease 追踪目标速度------不是匀速上升,而是「忽快忽慢、左右晃」。
融合 merge.ts:近距先弱吸引,触及时 30% 概率双爆、70% 进入 merge 状态机。resolveMergePose 分 approach / absorb 两阶段,体积按 ∛(r₁³+r₂³) 守恒,绘制时用 metaball 轮廓(与 BubbleField 同思路)。
爆裂 popBubble:14+ 颗 bead 水珠沿点击反方向飞出 + 28+ 颗 mist 微粒;pop 动画结束后 spawnBubble 从底部补新泡。
tsx
import { SoapBubbles } from '@cos-design/soap-bubbles';
<SoapBubbles fill count={28} speed={1} />
左:点击戳破,膜 fade 的同时水珠飞出。右:两泡靠近融合,颈部收缩成 metaball 连体。

一句话带走:虹彩来自薄膜厚度插值,灵魂在 merge 状态机------体积守恒 + metaball 绘制。
👉 试玩:#/soapBubbles
二、DandelionField:一整片会呼吸的生命循环
肥皂泡是「死物物理」;第二个组件我想做活物逻辑------不是粒子往上飘,而是一株株蒲公英真的在长高、开花、变绒球、被风吹散、再发芽。Playground 里等 intro 动画播完,你会看到草坡上一片 mature 绒球,那就是最佳阅读起点。

它解决什么问题?
「飘絮粒子」Demo 很多,但很少做成完整生态:发芽 → 开花 → 变绒球 → 被风吹散 → 种子落地 → 再发芽。DandelionField 要的是这个闭环,而且鼠标滑过要像真的在吹风。
核心实现:模块化生命周期
源码按职责拆成六个文件------v3.8.0 CHANGELOG 里专门提到的重构:
bash
dandelionField/
├── index.tsx # React 外壳、主循环、指针风场
├── plant.ts # 植株状态机:sprout → flower → puffing → mature → wither
├── seed.ts # 附着/脱离、飘飞、落地、发芽
├── draw.ts # 茎叶、绒球、种子、光晕
├── scene.ts # 静态背景 + 草簇风摆
└── frame-cache.ts # 植株几何缓存(每帧不算两次 head 位置)
植株状态机 是灵魂。以 mature 为例:先 matureHoldLeft 随机驻留,到期后 beginReleasing,按 scheduledRelease 时间轴不规则释放种子------外圈先飞、内圈后飞,而不是一次性爆炸。
指针风场 :pointermove 根据位移速度算 gust,扫过 mature/puffing 绒球且距离 < 105×scale 时,boostPlantRelease 加速散种;点击最近绒球则 boostPlantRelease(2.8) 整朵炸开。
种子发芽 :落地后延迟 germinateDelay,按 GERMINATION_CHANCE / GERMINATION_NEAR_CHANCE 决定是否在附近长出新株------离母株越近概率越高,视觉上像「一片草地慢慢蔓延」。
tsx
import { DandelionField } from '@cos-design/dandelion-field';
<DandelionField fill plantCount={10} seedCount={32} speed={1} />
左:快速划过绒球,风场加速散种。右:点击整朵炸开,种子刚离体的一瞬。

性能优化 (v3.8.0 重点):PlantFrameCache 缓存 head/life 几何;AttachedSeedTracker 用计数器替代每帧 filter 附着种子------植株多时帧率更稳。
一句话带走:这不是粒子系统,是带 germination 闭环的植株状态机 + 指针风场。
👉 试玩:#/dandelionField(建议先等 intro 播完再摸)
三、LavaBubble:CPU 算场,GPU 只看
前两个都是 Canvas 2D。第三个我想做「硬介质」------熔岩湖。难点不在颜色渐变,而在完整事件链:鼓起 → 变薄 → 闷裂 → 空腔 → 回填,还要能点击、能拖拽撕裂。这是五个组件里唯一上了 WebGL 的。

它解决什么问题?
熔岩/岩浆视觉在 Web 里常被做成 looping 视频或纯 shader 噪声------能看,但很难点击鼓起 → 壳裂 → 溅射 → 空腔回填 这一完整事件链。LavaBubble 拆成:CPU 192×192 场仿真 写纹理,WebGL 片段着色读高度/热量/空腔做法线光照。
核心实现:Blister 四阶段状态机
每个 blister 经历:
scss
inflate(壳层隆起)→ thin(变薄、裂纹发热)→ burst(撕裂空腔 + 溅射)→ cavity(回填、余温)
sim.ts 里维护三张标量场:
heightField:隆起高度heatF:热量(写回 shader 做辉度)cavityF:爆裂后的空腔深度
upload() 把 RGBA 纹理打包:R=height, G=heat, B=cavity。
片段着色器 shaders.ts 从纹理采样后:
calcNormal算法线(空腔会「挖深」法线)heatColor(t)六段渐变------最低也是暗红,避免纯黑熔壳- wrap lighting + 双层 specular + 空腔 rim 余温
交互两路:
- 点击
spawnBlister(u,v)在 UV 坐标鼓新泡 - 拖拽
applyStir注入涡旋、螺旋热纹、椭圆 shell crack,并stirBlister加速未爆泡的裂纹
tsx
import { LavaBubble } from '@cos-design/lava-bubble';
<LavaBubble fill heat={1} autoSpawn activity={1} speed={1} />
左:点击后 burst 相,空腔 + 溅射热纹。右:壳层隆起、裂纹发热的 thin 阶段。

为什么 CPU + GPU 分工? 气泡生命周期、溅射粒子(mist/bomb/crust 三类 spatter)、热扩散需要随机性和分支逻辑,放 CPU 更直观;全屏像素着色交给 GPU,192² 纹理足够表达「湖面的起伏与裂口」。
一句话带走:仿真在 CPU,观感在 GPU------纹理 RGB 三包 height / heat / cavity。
👉 试玩:#/lavaBubble(autoSpawn 开着等几秒,看随机鼓泡)
四、InkBloom:一滴墨,两种场
熔岩是「硬壳 + 碎裂」;第四个我想做完全相反的质感------软、慢、会累积。墨水滴进清水,溶开后整盆水越来越深------这种「越摸越深」的反馈,blur 圆叠不出来。

它解决什么问题?
「墨水扩散」特效常见做法是叠几张 blur 圆------像,但缺少溶开 和整片水被染深 的累积感。InkBloom 用轻量 2D 流体近似:密度场 + 速度场 + 持久染色场。
核心实现:三缓冲区 + 低分辨率仿真
仿真网格 simW × simH ≈ 画布 / 4,每格维护:
| 场 | 变量 | 作用 |
|---|---|---|
| 密度 | dens |
当前悬浮的浓/淡墨团 |
| 速度 | vx, vy |
平流 + 涡旋驱动 |
| 染色 | stain |
已溶入水中、几乎不退色的背景色 |
主循环(每帧):
scss
applyForces → 粘滞衰减 → advect(dens) → diffuseDensity
→ advect(velocity) → dens 溶入 stain → stain 缓慢铺匀
→ paintWater(avgStain) → renderStain → renderInk
点击注入 injectDrop 做三件事:不规则浓墨心(fbm 扰动边界)、3~6 个随机涡旋(切向速度)、外圈 22 点淡墨晕。拖拽 injectStir 把指针位移写进速度场。
力场里值得单独说的一句是涡度约束 ------从 curl 场反推侧向推力,拉出絮状细丝,避免每次扩散都是完美圆:
ts
const cx = (curl[i + 1] - curl[i - 1]) * 0.5;
const cy = (curl[i + simW] - curl[i - simW]) * 0.5;
vx[i] += (cy / len) * curl[i] * eps;
vy[i] -= (cx / len) * curl[i] * eps;
质量守恒的染色 :浓团 dissolve 后质量转入 stain,paintWater 根据 avgStain 把清水从 #c5dff0 渐变为 #12151a------点得越多,整盆水越深,这是和「一次性 blur 圆」最大的体验差异。
tsx
import { InkBloom } from '@cos-design/ink-bloom';
<InkBloom fill inkColor="#0c0e12" speed={1} />
上:单点滴墨,羽状晕开。下:连续多点后,整片清水被染深------注意背景色已从浅蓝变成深灰。


一句话带走 :dens 是悬浮墨团,stain 是已溶入水的永久染色------质量守恒,越点越深。
👉 试玩:#/inkBloom(连续点 5~8 次,等十秒看背景变深)
五、AuroraVeil:把极光画成「可弯曲的丝带」
五个里最后做、也最「登页面」的一个。极光背景烂大街,但多数要么像壁纸、要么 shader 调参劝退。我想用纯 Canvas 2D 做出光带能被鼠标弯曲、点击能爆发脉冲的北极夜------不引 WebGL,但要够「哇」。

它解决什么问题?
市面上「极光背景」常见两条路:CSS 渐变动画(轻,但像壁纸)或 WebGL 噪声场(真,但调参成本高)。AuroraVeil 选中间态:纯 Canvas 2D ,用逐行轮廓 描述光带,再用离屏 buffer + screen 混合叠出辉光。
核心实现:EdgeProfile 光带
每条光带(Veil)不是一张图片,而是一列 y → 中心 x、半宽 hw 的采样:
ts
// 每一行 y:正弦波叠加 drift + 指针磁吸 + 点击脉冲扰动
const centerX = (veil, y, time, sheet) => { /* wave + drift + pointer magnet */ };
const halfWidth = (veil, y, time, sheet) => { /* 指针附近收窄 */ };
// Float32Array 缓存左右边缘,拼成闭合 ribbon
const buildProfile = (veil, time, sheet): EdgeProfile => { /* leftBuf / rightBuf */ };
渲染分三层:
- paintSky:深空渐变 + 260 颗按深度闪烁的星 + 地平线暗角
- fillRibbon :水平/垂直双色渐变,
source-atop做垂向衰减 - 离屏合成 :主 sheet
screen叠一层,再 blur 2.4px 的 glowSheet 做柔辉
交互上,指针会磁吸弯曲 最近光带、收窄 经过区域的半宽;点击触发 burstRef 整屏 ripple + 最多 4 个扩散 pulse。
tsx
import { AuroraVeil } from '@cos-design/aurora-veil';
<AuroraVeil fill colors={['#7ee8d8', '#4cc9f0', '#9d8df1']} bandCount={5} speed={1} />

工程细节 :MAX_DPR = 2;prefers-reduced-motion 时 stars 停止 twinkle、光带冻结;与 BubbleField 一样走 @cos-design/shared 的 useCanvasBox。
一句话带走:光带不是贴图,是逐行 EdgeProfile + 离屏 glow 合成。
👉 试玩:#/auroraVeil(在光带中心点一下,看脉冲扩散)
六、五个组件怎么选?一张表说清楚
五个组件读下来,你可能已经在脑子里对上了场景。我把选型收成一张表------按 Playground 侧栏顺序排列,方便回去对照:
| 你要的气质 | 推荐组件 | 交互方式 | 渲染成本 |
|---|---|---|---|
| 童趣 / 夏日 / 轻活动 | SoapBubbles | 点击戳破、自动融合 | 中(纯 Canvas) |
| 治愈 / 春季活动 / 自然系 | DandelionField | 滑动吹风、点击炸开 | 中高(多实体 + 生命周期) |
| 硬核 / 游戏 / 暗黑 Hero | LavaBubble | 点击鼓泡、拖拽撕裂 | 中高(WebGL + CPU sim) |
| 国风 / 文艺 / 留白排版 | InkBloom | 点击滴墨、拖拽搅动 | 中(低分辨率场) |
| 科技感 / 登录屏 / 北欧夜 | AuroraVeil | 移动弯曲、点击脉冲 | 中(离屏 blur) |
还有一个 cos-design 一贯建议:一页只放一个强全屏动效背景 。第二个背景请降尺寸或关 interactive,否则抢视觉、也抢 GPU。
如果五个都想试一遍,Playground 侧栏从上往下点就行------和本文顺序完全一致。
七、工程化:分包、降级、Next.js
五个组件均已独立发布,可按需安装------不必为了一个肥皂泡拖进整个库:
bash
pnpm add @cos-design/soap-bubbles @cos-design/dandelion-field @cos-design/lava-bubble @cos-design/ink-bloom @cos-design/aurora-veil
# 或全量
pnpm add cos-design@3.8.0
Next.js App Router 记得动态导入 + ssr: false(Canvas/WebGL 仅客户端):
tsx
import dynamic from 'next/dynamic';
const SoapBubbles = dynamic(
() => import('@cos-design/soap-bubbles').then((m) => m.SoapBubbles),
{ ssr: false }
);
export default function Hero() {
return (
<section style={{ position: 'relative', minHeight: '100vh' }}>
<SoapBubbles fill />
{/* 前景内容 z-index 需高于 canvas */}
</section>
);
}
共享约定(v3.7 起逐步统一,五个新组件全部遵循):
fill:父级需有明确高度(如100vh),组件铺满bindVisibilityPause:切 tab 停 rAF,省电prefers-reduced-motion:静态帧,不黑屏- Playwright smoke:v3.8.0 为新组件补了 canvas 挂载测试
@cos-design/shared 本版还导出了 softSat 等数学工具,供后续组件复用。
收尾:从 Demo 到「介质库」
回头看 v3.8.0,这五个组件不是简单「+5」,而是在补 cos-design 背景线的介质维度:
- SoapBubbles → 薄膜
- DandelionField → 生命
- LavaBubble → 热/壳层
- InkBloom → 流体
- AuroraVeil → 光
它们和早期的 WeatherBackground、BubbleField、RippleWater 一起,构成一套「不用 Three.js 也能做重磅背景」的选项库。
如果你只来得及摸一个:夏日活动页戳 SoapBubbles,登录屏弯曲 AuroraVeil,国风留白滴 InkBloom。背景能不能留人,往往差的就是这一层可交互的介质感------读完这篇,打开 Playground 走一遍侧栏,比看十张截图都直观。
链接与延伸阅读
| 资源 | 地址 |
|---|---|
| Playground | jiaxiantao.xyz/cos-design/... |
| SoapBubbles | jiaxiantao.xyz/cos-design/... |
| DandelionField | jiaxiantao.xyz/cos-design/... |
| LavaBubble | jiaxiantao.xyz/cos-design/... |
| InkBloom | jiaxiantao.xyz/cos-design/... |
| AuroraVeil | jiaxiantao.xyz/cos-design/... |
| GitHub | github.com/jiaxiantao/... |
| npm | www.npmjs.com/package/cos... |
同系列文章