Confetti
Installation
The confetti is built on canvas confetti.
npm install --save canvas-confetti
Copy and paste the following code into your project.
components/magicui/confetti.tsx
import { Button, ButtonProps } from "@/components/ui/button";
import confetti from "canvas-confetti";
import type { ReactNode } from "react";
import React, {
createContext,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from "react";
import type {
GlobalOptions as ConfettiGlobalOptions,
Options as ConfettiOptions,
} from "canvas-confetti";
import type { CreateTypes as ConfettiInstance } from "canvas-confetti";
type Api = {
fire: (options?: ConfettiOptions) => void;
};
type Props = React.ComponentPropsWithRef<"canvas"> & {
options?: ConfettiOptions;
globalOptions?: ConfettiGlobalOptions;
manualstart?: boolean;
children?: ReactNode;
};
export type ConfettiRef = Api | null;
const ConfettiContext = createContext<Api>({} as Api);
const Confetti = forwardRef<ConfettiRef, Props>((props, ref) => {
const {
options,
globalOptions = { resize: true, useWorker: true },
manualstart = false,
children,
...rest
} = props;
const instanceRef = useRef<ConfettiInstance | null>(null); // confetti instance
const canvasRef = useCallback(
// https://react.dev/reference/react-dom/components/common#ref-callback
// https://reactjs.org/docs/refs-and-the-dom.html#callback-refs
(node: HTMLCanvasElement) => {
if (node !== null) {
// <canvas> is mounted => create the confetti instance
if (instanceRef.current) return; // if not already created
instanceRef.current = confetti.create(node, {
...globalOptions,
resize: true,
});
} else {
// <canvas> is unmounted => reset and destroy instanceRef
if (instanceRef.current) {
instanceRef.current.reset();
instanceRef.current = null;
}
}
},
[globalOptions],
);
// `fire` is a function that calls the instance() with `opts` merged with `options`
const fire = useCallback(
(opts = {}) => instanceRef.current?.({ ...options, ...opts }),
[options],
);
const api = useMemo(
() => ({
fire,
}),
[fire],
);
useImperativeHandle(ref, () => api, [api]);
useEffect(() => {
if (!manualstart) {
fire();
}
}, [manualstart, fire]);
return (
<ConfettiContext.Provider value={api}>
<canvas ref={canvasRef} {...rest} />
{children}
</ConfettiContext.Provider>
);
});
interface ConfettiButtonProps extends ButtonProps {
options?: ConfettiOptions &
ConfettiGlobalOptions & { canvas?: HTMLCanvasElement };
children?: React.ReactNode;
}
function ConfettiButton({ options, children, ...props }: ConfettiButtonProps) {
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
const rect = event.currentTarget.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
confetti({
...options,
origin: {
x: x / window.innerWidth,
y: y / window.innerHeight,
},
});
};
return (
<Button onClick={handleClick} {...props}>
{children}
</Button>
);
}
export { Confetti, ConfettiButton };
export default Confetti;
Examples
Basic
Random Direction
Fireworks
Side Cannons
Stars
Custom Shapes
Emoji
Props
Confetti
Prop | Type | Description | Default |
---|---|---|---|
particleCount | Integer | The number of confetti particles to launch | 50 |
angle | Number | The angle in degrees at which to launch confetti | 90 |
spread | Number | The spread in degrees of the confetti | 45 |
startVelocity | Number | The initial velocity of the confetti | 45 |
decay | Number | The rate at which confetti slows down | 0.9 |
gravity | Number | The gravity applied to confetti particles | 1 |
drift | Number | The horizontal drift applied to particles | 0 |
flat | Boolean | Whether confetti particles are flat | false |
ticks | Number | The number of frames confetti lasts | 200 |
origin | Object | The origin point of the confetti | { x: 0.5, y: 0.5 } |
colors | Array of Strings | Array of color strings in HEX format | ['#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff'] |
shapes | Array of Strings | Array of shapes for the confetti | ['square', 'circle'] |
zIndex | Integer | The z-index of the confetti | 100 |
disableForReducedMotion | Boolean | Disables confetti for users who prefer no motion | false |
useWorker | Boolean | Use Web Worker for better performance | true |
resize | Boolean | Whether to resize the canvas | true |
canvas | HTMLCanvasElement or null | Custom canvas element to draw confetti | null |
scalar | Number | Scaling factor for confetti size | 1 |
ConfettiButton
Prop | Type | Description | Default |
---|---|---|---|
options | Object | Options for the confetti | |
children | React.ReactNode | Children to render inside the button | null |
Credits
- Credit to Bankk
- Inspired by canvas-confetti