Sawana Huang Avatar

Sawana Huang

如何在 React / Next.js 中正确使用 canvas-confetti(2026 版)

Sawana Huang - Fri Aug 01 2025

按当前官方资料重写的 canvas-confetti 教程,讲清楚它和 react-canvas-confetti 的关系、TypeScript 安装方式、Next.js 客户端用法,以及更稳妥的礼花按钮实现。

这篇文章把旧文里最容易绕人的地方重新理了一遍,主要是这两点:

  • canvas-confettireact-canvas-confetti 说成了“二选一的替代关系”,其实它们是不同层级的东西
  • 默认读者去复制整段示例网站代码,但没有先讲清楚 Next.js 里最关键的前提:这是一个浏览器端动画库,只能在客户端组件里调用

如果你的目标只是“点一下按钮,或者任务完成后放一段礼花”,canvas-confetti 往往就够了。

先给一版能跑的写法

现在在 React / Next.js 里用 canvas-confetti,我会按这个顺序来:

  1. 安装 canvas-confetti
  2. 如果是 TypeScript,再安装 @types/canvas-confetti
  3. 把调用代码放进 "use client" 组件
  4. 先从一个小函数开始,不要一上来就复制整段官网长动画
  5. 优先加上 disableForReducedMotion: true

先从这个小例子开始就行:

"use client";

import confetti from "canvas-confetti";

export function CelebrateButton() {
  return (
    <button
      onClick={() =>
        void confetti({
          particleCount: 120,
          spread: 70,
          origin: { y: 0.7 },
          disableForReducedMotion: true,
        })
      }
    >
      Celebrate
    </button>
  );
}

canvas-confettireact-canvas-confetti 到底是什么关系

这里先把概念摆正:

  • canvas-confetti
    • 官方维护的底层浏览器动画库
    • 你直接调用 confetti(...)confetti.create(...)
  • react-canvas-confetti
    • React 封装层
    • 帮你把 canvas 生命周期包装成组件风格

所以问题不该是“谁替代谁”,而是:

  • 如果你只是在按钮点击后放一个庆祝特效,用 canvas-confetti 往往更简单
  • 如果你需要自己完全控制 canvas 节点、实例生命周期或复杂封装,再考虑 React wrapper

对多数产品页面来说,直接用官方库就够了。

TypeScript 项目要不要装 @types/canvas-confetti

要。

我在当前项目里确认过,[email protected] 的包信息里没有自带 types 字段,所以 TypeScript 项目继续安装 @types/canvas-confetti 仍然是合理做法。

完整接入步骤

1. 安装依赖

参考 canvas-confetti 官方仓库

npm install canvas-confetti
npm install -D @types/canvas-confetti

如果你不是 TypeScript 项目,第二行可以省略。

2. 明确:它只能运行在客户端

官方 README 已经说得很直接:这是一个浏览器端库,不会在 Node 环境里运行。

在 Next.js 里,这意味着:

  • 调用它的文件要是客户端组件
  • 文件顶部写 "use client"
  • 不要在 Server Component、Route Handler、Server Action 里直接调用动画

一个最小可用按钮如下:

"use client";

import confetti from "canvas-confetti";

export function CelebrateButton() {
  function handleClick() {
    void confetti({
      particleCount: 120,
      spread: 70,
      origin: { y: 0.7 },
      disableForReducedMotion: true,
    });
  }

  return <button onClick={handleClick}>Celebrate</button>;
}

3. 先从“小函数”开始,不要一上来就复制官网整段长动画

官网 demo 很有用,更适合当“效果素材库”,没必要每次都整段照搬。

在真实项目里,我更建议先抽一个可复用的小函数:

import confetti from "canvas-confetti";

export function burstConfetti() {
  return confetti({
    particleCount: 160,
    spread: 90,
    startVelocity: 40,
    origin: { y: 0.7 },
    disableForReducedMotion: true,
  });
}

然后在任何按钮、成功提示或完成态里复用它:

"use client";

import { burstConfetti } from "./burst-confetti";

export function SuccessButton() {
  return <button onClick={() => void burstConfetti()}>Done</button>;
}

这样后面你想统一调整粒子数量、速度、颜色,不需要全项目到处找代码。

4. 如果你想把礼花限制在某个区域,用 confetti.create

参考官方 README 的 confetti.create(canvas, options)

这适合两类场景:

  • 你不想让礼花覆盖整页
  • 你想把礼花绑定在卡片、模态框、面板等局部区域
"use client";

import { useEffect, useRef } from "react";
import confetti from "canvas-confetti";

export function PanelConfetti() {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);

  useEffect(() => {
    if (!canvasRef.current) return;

    const fire = confetti.create(canvasRef.current, {
      resize: true,
      useWorker: true,
      disableForReducedMotion: true,
    });

    void fire({
      particleCount: 80,
      spread: 100,
    });
  }, []);

  return (
    <canvas ref={canvasRef} className="pointer-events-none absolute inset-0" />
  );
}

官方也特别提醒了两点:

  • useWorker: true 时,这个 canvas 的控制权会转交给 worker
  • 如果你启用了 worker,就不要再试图在主线程里继续操作这块 canvas

5. 尊重用户的 reduced motion 偏好

这是旧文里完全没提,但我现在认为应该默认加入的选项。

官方库提供了:

disableForReducedMotion: true;

加上它之后,如果用户系统偏好减少动态效果,礼花会自动关闭,不会硬播出来。

这类庆祝动画本来就是“锦上添花”的交互,不值得拿可访问性去换。

一个贴近本仓库的实际示例

当前仓库里已经有一个 demo 组件:

它的实现思路其实就是本文推荐的方向:

  • 使用客户端组件
  • 直接调用 canvas-confetti
  • 把一次礼花拆成多次不同参数的 fire(...)

如果你想继续优化它,我最建议优先做这两件事:

  • 加上 disableForReducedMotion: true
  • ConfettiAction 提取成独立 helper,避免按钮组件和动画逻辑耦合得太死

常见误区

1. “React 里就必须用 React wrapper”

不是。

很多时候你只是想在一个点击事件里放礼花,这种场景直接用官方库反而最干净。

2. “只要能跑起来,就不用管 reduced motion”

这是不值得省的那一行配置。

3. “Next.js 里不生效,说明库有问题”

大多数时候问题不在库本身,通常出在这里:

  • 你忘了 "use client"
  • 你在服务端执行了浏览器动画
  • 你把逻辑写在了不会触发浏览器事件的地方

4. “官网 demo 代码必须整段复制”

官方 demo 很适合找灵感,但真实项目里更重要的是抽出适合你业务的最小动画函数。

总结

这类动效先要把边界站稳:客户端组件、一个小而清楚的封装、再加上 reduced motion。这些打稳了,后面你想换效果、加局部 canvas,或者拆成更复杂的 helper,都会顺很多。

参考资料

作者:Sawana Huang
发布时间:2025年8月1日

声明: 本文采用CC BY-NC-SA 4.0许可协议,转载请注明出处。