气泡背景特效(Canvas 气泡上升动画,可点击戳破)

CANVASJS 许可:自研原创

Canvas 气泡背景特效:半透明气泡从底部升起并左右摇曳,点击可戳破并扩散消失,数量、速度、大小实时可调,已适配高分屏,复制即用。

效果演示(真实可交互)

气泡从底部缓缓升起,边上升边按正弦轨迹左右摇曳,模拟水中浮力与流体的扰动。直接点击画面中的气泡会触发「戳破」动画:气泡位置扩散出一个逐渐变淡的圆环,同时底部立即补上一颗新气泡,保证画面密度恒定。演示面板可实时调整气泡数量(10-80)、上升速度(1-10)和最大半径(10-40px)。每个气泡带相位偏移和独立的摇曳幅度,因此即使数量很多也不会出现整齐划一的”僵尸队列”感。整个实现只依赖一个 canvas 与 requestAnimationFrame 循环,无任何第三方库。

点击气泡即可戳破 ✨

完整代码

<canvas id="bubble" style="width:100%;height:340px;display:block;background:linear-gradient(180deg,#0e1a3a,#0b2a4a);"></canvas>
const canvas = document.getElementById('bubble');
const ctx = canvas.getContext('2d');
let W, H, bubbles = [], popping = [];
const cfg = { count: 30, speed: 4, maxR: 22 };

function resize() {
  const r = canvas.getBoundingClientRect();
  const dpr = window.devicePixelRatio || 1;
  canvas.width = Math.round(r.width * dpr);   // 物理像素
  canvas.height = Math.round(r.height * dpr);
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);     // 之后一律用 CSS 像素绘图
  W = r.width; H = r.height;
}
function spawn() {
  return {
    x: Math.random() * W, y: H + 30,
    r: Math.random() * cfg.maxR + 6,
    speed: (Math.random() * .6 + .4) * cfg.speed,
    phase: Math.random() * Math.PI * 2,       // 摇曳相位
    wobble: Math.random() * 20 + 8,           // 摇曳幅度
  };
}
function init() {
  bubbles = [];
  for (let i = 0; i < cfg.count; i++) {
    const b = spawn(); b.y = Math.random() * H; bubbles.push(b);
  }
}
function drawBubble(b, alpha) {
  ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
  ctx.fillStyle = `rgba(140,190,255,${.08 * alpha})`; ctx.fill();
  ctx.strokeStyle = `rgba(170,210,255,${.5 * alpha})`; ctx.lineWidth = 1.2; ctx.stroke();
  ctx.beginPath(); ctx.arc(b.x - b.r * .35, b.y - b.r * .35, b.r * .18, 0, Math.PI * 2);
  ctx.fillStyle = `rgba(255,255,255,${.7 * alpha})`; ctx.fill(); // 高光
}
function step() {
  ctx.clearRect(0, 0, W, H);
  for (const b of bubbles) {
    b.y -= b.speed; b.phase += .02;
    b.x += Math.sin(b.phase) * .8;
    if (b.y < -b.r - 20) Object.assign(b, spawn());
    drawBubble(b, 1);
  }
  for (let i = popping.length - 1; i >= 0; i--) {   // 戳破扩散环
    const p = popping[i];
    p.r += p.grow; p.alpha -= .04;
    if (p.alpha <= 0) { popping.splice(i, 1); continue; }
    ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
    ctx.strokeStyle = `rgba(180,215,255,${p.alpha * .8})`; ctx.lineWidth = 2; ctx.stroke();
  }
  requestAnimationFrame(step);
}
resize(); init(); step();
window.addEventListener('resize', () => { resize(); init(); });

// 点击戳破
canvas.addEventListener('click', e => {
  const r = canvas.getBoundingClientRect();
  const mx = e.clientX - r.left, my = e.clientY - r.top;
  for (let i = bubbles.length - 1; i >= 0; i--) {
    const b = bubbles[i];
    if (Math.hypot(b.x - mx, b.y - my) < b.r + 4) {
      popping.push({ x: b.x, y: b.y, r: b.r, grow: 1.2, alpha: 1 });
      bubbles.splice(i, 1); bubbles.push(spawn()); break;
    }
  }
});

参数说明

参数位置说明
气泡数量cfg.count10-80,演示面板实时生效
上升速度cfg.speed1-10,越大升得越快
最大半径cfg.maxR10-40px,实际半径在其 0-100% 间随机
摇曳幅度b.wobble默认 8-28px,越大摆动越明显
气泡颜色rgba(...) 三处填充、描边、高光的色值可整体替换

兼容性与性能

常见问题

Q: 气泡为什么从底部冒出来? 因为初始位置 y = H + 30 在画布下边缘之外,随 y 逐帧减小进入可视区,形成”上浮”观感;底部补位同样复用 spawn()。

Q: 想让气泡更真实一点? 可以给每个气泡叠加一个椭圆形的二次高光,或让半径随高度轻微变化(越靠近水面越大),这些都在 drawBubble 内即可完成。

Q: 气泡背景和 CSS 动画气泡有什么区别? CSS 方案每个气泡都要一个 DOM 节点,几十个就很吃力;Canvas 方案所有气泡共用一个画布,数量翻倍几乎不增加 DOM 开销,适合做全屏背景。

Q: 页面滚动时动画会不会卡? 画布固定尺寸且循环只画自身内容,滚动卡顿通常来自页面其他重排;建议给画布容器加 will-change: transform 或 contain: paint 隔离。

许可与来源

  • 许可证:自研原创 —— 粤喵自研原创,可免费用于个人与商业项目,无需署名。查看来源公示与许可清单
  • 来源:粤喵团队原创自研(从效果思路到代码实现均为独立完成,未直接复制第三方代码)
  • 首次发布:2026-09-01 · 最近更新:2026-09-01
  • 修改日志:2026-09-01 首次发布;2026-09-13 补充许可证与来源标注、复制按钮说明与法务页入口
  • 纠错:联系页面 / min@cactus.asia,2 个工作日内核实处理

常见问题

这个组件可以商用吗?

可以。本组件为粤喵自研原创,可免费用于个人和商业项目,无需署名。

浏览器兼容性如何?

支持所有现代浏览器(Chrome / Firefox / Safari / Edge 最新版本)。旧版 IE 不支持 CSS 动画相关特性。

怎么修改动画参数?

直接调整 CSS 中的 animation-duration(时长)、animation-timing-function(缓动曲线)等属性即可,代码中有注释说明。

✍️ 作者与审核