如何在 React / Next.js 中正确使用 canvas-confetti(2026 版)
Sawana Huang - Fri Aug 01 2025
按当前官方资料重写的 canvas-confetti 教程,讲清楚它和 react-canvas-confetti 的关系、TypeScript 安装方式、Next.js 客户端用法,以及更稳妥的礼花按钮实现。
这篇文章把旧文里最容易绕人的地方重新理了一遍,主要是这两点:
- 把
canvas-confetti和react-canvas-confetti说成了“二选一的替代关系”,其实它们是不同层级的东西 - 默认读者去复制整段示例网站代码,但没有先讲清楚 Next.js 里最关键的前提:这是一个浏览器端动画库,只能在客户端组件里调用
如果你的目标只是“点一下按钮,或者任务完成后放一段礼花”,canvas-confetti 往往就够了。
先给一版能跑的写法
现在在 React / Next.js 里用 canvas-confetti,我会按这个顺序来:
- 安装
canvas-confetti - 如果是 TypeScript,再安装
@types/canvas-confetti - 把调用代码放进
"use client"组件 - 先从一个小函数开始,不要一上来就复制整段官网长动画
- 优先加上
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-confetti 和 react-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. 安装依赖
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,都会顺很多。