cos-design BubbleField:用 Canvas 做一个「会呼吸」的深海气泡场

cos-design BubbleField:用 Canvas 做一个「会呼吸」的深海气泡场

发布日期:2026-07-28

标签:前端 / React / Canvas / 组件库 / cos-design / 视觉特效 / 物理仿真

活动页、品牌 Landing、游戏氛围页,常常需要一层「水下感」:气泡从海底升起、靠近时融成一团、鼠标划过时被水流带偏。很多项目会临时找一段粒子 Demo------圆点往上飘,看起来像汽水,不像深海。

BubbleField 是我在 cos-design v3.6.0 新增的背景动效组件:纯 Canvas 2D,无 Three.js / WebGL。对外几个 props,对内拆成物理、融合、渲染、背景四层模块------浮力上升、表面振荡、体积守恒融合、指针尾流扰动。本文讲清技术实现,并方便你直接装包试用。

Playgroundjiaxiantao.xyz/cos-design/...

仓库github.com/jiaxiantao/...

独立包@cos-design/bubble-field@3.6.0

全量包cos-design@3.6.0


效果预览


一、它解决什么问题?

常见「气泡背景」有三条路:

方案 优点 痛点
CSS 圆点 / 简单粒子 轻量 没有变形、融合、水流感
现成粒子 Demo 上手快 一份效果一份代码,难调参
Three.js / 流体仿真 表现力强 依赖重,活动页往往过重

BubbleField 选的是中间态:Canvas 2D + 轻量物理配方。对外是一个组件:

tsx 复制代码
import { BubbleField } from '@cos-design/bubble-field';

<BubbleField width={800} height={500} bubbleCount={36} speed={1} />

对内不是「一堆 arc 往上挪」,而是:

海底生成 → 浮力上升 → 表面模态变形 → 相近体积守恒融合 → 鼠标划过制造尾流。


二、整体架构:四个模块各管一层

源码按职责拆开,适合阅读,也适合扩展:

bash 复制代码
bubbleField/
├── index.tsx      # React 外壳、rAF、指针、生成调度
├── physics.ts     # 浮力、环境流、指针尾流、表面模态
├── merge.ts       # 空间哈希、短距吸引、体积守恒融合
├── render.ts      # 可变形轮廓 Path2D、metaball 轮廓、光照
├── background.ts  # 静态背景缓存、God rays、海洋雪
├── utils.ts       # frameDamp、easing、终端上升速度
└── types.ts       # Bubble / Merge / Pointer 类型

主循环顺序可以概括成:

sql 复制代码
画动态背景 → 按节奏从海底 spawn
  → 指针尾流扰动(可选)
  → 积分运动 + 表面模态
  → 短距吸引 / 启动融合 / 更新融合姿态
  → 按 y 深度排序绘制(单体 blob / 融合 metaball)

这和游戏里的「物理 tick → 碰撞/合并 → 渲染」同一套心智。


三、Props:少而够用

ts 复制代码
export interface BubbleFieldProps {
  width?: number;        // 默认 800
  height?: number;       // 默认 500
  bubbleCount?: number;  // 气泡数量上限,默认 36
  speed?: number;        // 上浮速度倍率,默认 1
  color?: string;        // 水体主色 / tint,默认 '#7dd3fc'
  interactive?: boolean; // 鼠标划过扰动,默认 true
}

交互开启时底部提示是「海底气泡上升 · 划过扰动水流」;关掉则只保留氛围层。


四、关键算法①:浮力上升,而不是匀速上飘

每颗气泡有 terminalRise(与半径相关的终端上升速度)。积分时用弹簧式加速度追这个目标,靠近水面再给一点 depthBoost,并叠加轻微 wobble / sway 与环境流场:

ts 复制代码
const depthBoost = 1 + (1 - depth) * 0.12;
const targetUp = bubble.terminalRise * speedScale * depthBoost;
const accel = (targetUp - upSpeed) * (0.022 + bubble.radius * 0.0011);
bubble.vy -= accel * frameScale;
// + 正弦晃动 + ambientFlow + frameDamp 阻尼

观感上:小泡升得慢且稳,大泡更「鼓」;不是所有圆点同一速度排队上浮。

从海底 spawn 时,半径也有偏置------多数中小,偶尔偏大------场里才有主次。


五、关键算法②:傅里叶表面模态 = 假软体

气泡没有网格、没有逐顶点弹簧。形变靠两阶表面模态(谐波振荡器):

\\ddot{m} = -\\omega\^2 m - d\\dot{m}

  • mode2:椭圆振荡,(\omega_2 \propto 1/\sqrt{r})(大气泡更「肉」)
  • mode3:三倍角非对称抖动
  • streamStretch:沿相对流速拉伸轮廓

画轮廓时,对 48 个角度采样半径:

ts 复制代码
const blobRadius = (bubble, angle, time) => {
  let local = breathe + ripple + pulseBoost + settle;
  // 沿流拉伸:面向流向变长,侧向略收
  local += facing * facing * s - (1 - facing * facing) * s * 0.55;
  local += mode2 * Math.cos(2 * (angle - mode2Angle)) * 0.28;
  local += mode3 * Math.cos(3 * (angle - mode3Phase)) * 0.16;
  return bubble.radius * (1 + local);
};

再旋转 / 压扁成 Path2D,叠径向渐变高光(光源约在右上)。这是 BubbleField「像果冻、像水下气泡」而不是「硬圆」的关键。


六、关键算法③:指针 = 局部尾流,不是简单排斥

samplePointerFlow 在光标附近用高斯衰减叠三种力:

分量 作用
drag 沿指针切向拖拽
vortex 侧向旋流(tanh(side)
pressure 径向压力

并按指针速度放大影响半径。同时把 strain / excite 灌进表面模态,让「水流扫过」和「外形晃一下」连在一起。

这比 bubble.x += dx * 0.1 那种推开,更像水下尾流。


七、关键算法④:体积守恒融合 + Metaball 轮廓

相近气泡会自动融合,流程分三段:

  1. 空间哈希 (cell ≈ MAX_RADIUS * 2.6)做近邻,避免 O(n²);
  2. 短距吸引:刚要碰上时轻轻拉近;
  3. 启动融合:目标半径按体积守恒:
ts 复制代码
const targetRadius = Math.min(
  MAX_RADIUS,
  Math.cbrt(primary.radius ** 3 + secondary.radius ** 3)
);

融合姿态分 approach(靠近)absorb(吸收) 两段缓动:主泡变大、副泡缩小,同时用 metaball 场画「两球粘连」的公共轮廓:

ts 复制代码
// 经典 r²/d² 场
metaballField = (ar*ar)/d1 + (br*br)/d2;
// 每个角度沿射线二分搜索场值 ≈ 1 的等值面点

Canvas 2D 上做双体 metaball,比 WebGL 等值面轻一个数量级,观感足够「融成一团」。


八、背景与观感:深海不用 3D

背景也分层,控制成本:

做法
静态深度渐变 / 暗角 离屏 canvas 缓存,仅尺寸或 color 变时重画
God rays 动态、screen 叠色,模拟从水面透下的光柱
海洋雪 单张柔边精灵 drawImage,数量按画布面积封顶

气泡按 y 从深到浅排序绘制,远近叠压更自然。整体气质是「深海气泡场」,不是 UI 上的蓝色圆点雨。


九、性能与工程约束

手段 作用
默认 ~36 气泡 路径采样与光照成本可预期
空间哈希融合 近邻查询近似 O(n)
静态背景缓存 避免每帧重绘大面积渐变
DPR ≤ 2 高分屏不过度放大 fill-rate
bindVisibilityPause 切标签页停仿真
deltaMs 钳制 + frameDamp 帧率无关阻尼,掉帧不「飞」
prefers-reduced-motion 降速、弱化指针与光柱

使用建议与 cos-design 其它 Canvas 背景一致:一页一个强全屏动效bubbleCount 过大或画布很大时先在真机上看帧率。


十、怎么用:安装与场景示例

10.1 安装

bash 复制代码
# 只要气泡场
pnpm add @cos-design/bubble-field

# 或整库
pnpm add cos-design

样式随包自动注入,无需再 import CSS。

10.2 典型用法

tsx 复制代码
import { BubbleField } from '@cos-design/bubble-field';
// 或:import { BubbleField } from 'cos-design';

<BubbleField
  width={960}
  height={540}
  bubbleCount={40}
  speed={1.2}
  color="#7dd3fc"
  interactive
/>

10.3 Next.js

依赖浏览器 API,SSR 请关掉:

tsx 复制代码
import dynamic from 'next/dynamic';

const BubbleField = dynamic(
  () => import('@cos-design/bubble-field').then((m) => m.BubbleField),
  { ssr: false }
);

10.4 适合放哪里?

  • 海洋 / 潜水 / 清凉主题活动页 Hero
  • 品牌 Landing 的氛围底层
  • 游戏、互动展的水下场景装饰
  • 需要「可划过扰动」的趣味背景

不适合:首屏字节极度敏感、完全不需要动效的纯文档站或重度表单后台。


十一、和 RippleWater / SmokeFog 怎么选?

同属 cos-design 背景动效,气质不同:

组件 介质 交互隐喻 渲染
RippleWater 水面 点击产生涟漪 WebGL 高度场
SmokeFog 雾 / 烟 点击拨散 Canvas 精灵
BubbleField 水下气泡 划过扰动水流 + 自动融合 Canvas 物理 + metaball

要「点水面」用 RippleWater;要「拨雾」用 SmokeFog;要「深海气泡升起来、还能融」用 BubbleField。


结语

BubbleField 想证明的不是「Canvas 能画圆」,而是:

  1. 浮力 + 帧率无关阻尼:上升有物理感,不是匀速粒子;
  2. 表面模态假软体:不用网格也能做出果冻形变;
  3. 体积守恒融合 + metaball:融合像一团水,而不是两圆叠透明度;
  4. 指针尾流:拖拽 + 旋流 + 压力,划过才有「水流」;
  5. 模块化与工程细节:可分包发布,可见性暂停与 reduced-motion 开箱即有。

如果你正在做海洋主题或氛围型 Landing,打开 Playground 划两下比看文档更快:

jiaxiantao.xyz/cos-design/...

bash 复制代码
pnpm add @cos-design/bubble-field

欢迎 Issue / PR,一起把背景动效做得更完整。


系列延伸阅读


参考

资源 链接
Playground jiaxiantao.xyz/cos-design/...
GitHub github.com/jiaxiantao/...
npm 全量包 www.npmjs.com/package/cos...
npm 子包 www.npmjs.com/package/@co...
Metaball 场 经典 (r^2/d^2) 等值面思路
表面模态 气泡 / 液滴形变常用的低阶傅里叶模态

相关推荐
随风一样自由1 小时前
【前端+登录页】登录页背景性能优化实战:从 722 KB 到 200 KB,LCP 从 2.5s 到 1.2s
前端·性能优化·登录页·ttl·lcp
swipe1 小时前
09|(前端转全栈)商品为什么不能随便上下架?后端状态机思维入门
前端·后端·全栈
taocarts_bidfans2 小时前
Taoify 站点访问地区限制与 IP 管控配置
前端·javascript·tcp/ip·taoify
狂师2 小时前
用自然语言控制手机,一条命令让 AI 帮你操作 Android 和 iOS 设备!
前端·开源·测试
Csvn2 小时前
# 🎨 CSS Container Queries 实战踩坑——从「响应式」到「容器式」
前端
zhangjw342 小时前
第36篇:Spring Boot进阶:Web开发+参数校验+全局异常处理
前端·spring boot·后端
老王以为2 小时前
解剖 Claude Code:逆向工程视角下的入口架构分析
前端·ai编程·claude
Csvn2 小时前
💰 JavaScript 浮点数精度问题深度剖析——前端金额计算的「定时炸弹」
前端
Csvn2 小时前
structuredClone:原生深拷贝 API 的正确打开方式
前端