Confetti
Confetti animations are best used to delight your users when something special happens
Confetti animations are best used to delight your users when something special happens
npm install canvas-confetti
import type { ReactNode } from "react";import React, {createContext,forwardRef,useCallback,useEffect,useImperativeHandle,useMemo,useRef,} from "react";import type {GlobalOptions as ConfettiGlobalOptions,CreateTypes as ConfettiInstance,Options as ConfettiOptions,} from "canvas-confetti";import confetti from "canvas-confetti";import { Button, ButtonProps } from "@/components/ui/button";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 instanceconst 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 instanceif (instanceRef.current) return; // if not already createdinstanceRef.current = confetti.create(node, {...globalOptions,resize: true,});} else {// <canvas> is unmounted => reset and destroy instanceRefif (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>);}Confetti.displayName = "Confetti";export { Confetti, ConfettiButton };export Confetti;
Confetti
Prop | Type | Description | Default |
---|---|---|---|
options | ConfettiOptions | Options for configuring individual confetti animations, such as colors, shapes, and particle count. | undefined |
globalOptions | ConfettiGlobalOptions | Global options for confetti instance, including settings for resizing and using a worker. | { resize: true, useWorker: true } |
manualstart | boolean | If | false |
children | ReactNode | Optional children nodes to render inside the | undefined |
ref | ConfettiRef | Ref object to access | null |
ConfettiButton
Prop | Type | Description | Default |
---|---|---|---|
options | ConfettiOptions & ConfettiGlobalOptions & { canvas?: HTMLCanvasElement } | Additional confetti options that apply when the button is clicked. | undefined |
children | React.ReactNode | The content to display within the button. | undefined |
...props | ButtonProps | Additional properties passed to the underlying | undefined |