typegpu-confetti

repository·main·Indexed 41 days ago

https://github.com/software-mansion-labs/typegpu-confetti

A React Native package for highly-customizable, GPU-accelerated confetti animations. It offloads complex particle simulations to the GPU using TypeGPU and WebGPU, providing a Confetti component and a useConfetti hook for imperative control. Supports custom gravity and particle initialization via GPU functions and the unplugin-typegpu Babel plugin.

Tokens
5.1K
Snippets
18
Records
20
Agent score
51%

What's inside typegpu-confetti

  1. Install typegpu-confetti in React Native

    main

    To use typegpu-confetti in a React Native project, follow these installation steps:

    1. Install react-native-wgpu: This is a required dependency for GPU rendering. Note that this package is not supported by Expo Go; you must use expo prebuild.

      npm install react-native-wgpu
    2. Install typegpu-confetti:

      npm install typegpu-confetti
    3. Install unplugin-typegpu (Optional but Recommended): If you want to pass JavaScript functions marked with the "use gpu" directive (such as custom gravity or initParticle functions) to the component, you must include this Babel plugin in your project.

      npm install unplugin-typegpu
    npm install react-native-wgpu
    npm install typegpu-confetti
    npm install unplugin-typegpu
  2. Use the useConfetti hook (Recommended)

    main

    The recommended way to trigger confetti is using the useConfetti hook. This approach requires wrapping a high-level component (ideally at the top of your app) with the ConfettiProvider to ensure the animation covers the screen and the hook is accessible throughout the component tree.

    Steps:

    1. Wrap your app with ConfettiProvider.
    2. Call useConfetti() in a descendant component.
    3. Use the returned ref to control the animation via imperative methods.

    Available methods on the ref:

    • pause(): Pauses the animation.
    • resume(): Resumes the animation.
    • restart(): Restarts the animation.
    • addParticles(amount: number): Adds a specific number of particles to the current simulation. Calling this resets the maxDurationTime counter to zero.
    import { ConfettiProvider, useConfetti } from 'typegpu-confetti/react-native';
    
    // 1. Wrap your top-level component
    function App() {
      return (
        <ConfettiProvider>
          <SomeInnerComponent />
        </ConfettiProvider>
      );
    }
    
    // 2. Use the hook in a descendant
    function SomeInnerComponent() {
      const confettiRef = useConfetti();
    
      return (
        <Button title="run confetti" onPress={() => confettiRef?.current?.addParticles(50)} />
      );
    }
  3. Use the Confetti component directly

    main

    If you do not want to use the hook/provider pattern, you can use the Confetti component directly.

    Behavior:

    • The component is positioned absolutely and covers its closest parent with position: 'relative' (which is the default in React Native).
    • You can control it imperatively using a ref of type ConfettiRef.

    Imperative Control Example:

    import React, { useRef } from 'react';
    import { View, Button } from 'react-native';
    import type { ConfettiRef } from 'typegpu-confetti';
    import { Confetti } from 'typegpu-confetti/react-native';
    
    function SomeComponent() {
      const ref = useRef<ConfettiRef>(null);
    
      return (
        <View style={{ flex: 1 }}>
          <Confetti 
            initParticleAmount={200} 
            maxParticleAmount={1000} 
            ref={ref} 
          />
          <Button 
            title="add particles" 
            onPress={() => ref.current?.addParticles(200)} 
          />
        </View>
      );
    }
  4. Configure Metro for monorepo support in test-app

    main

    The test-app uses a Metro configuration designed for a monorepo environment. It ensures that Metro watches the entire monorepo workspace and correctly resolves node_modules from both the local project directory and the monorepo root. It also enables unstable_enablePackageExports to support modern package export patterns.

    Key configuration settings:

    • watchFolders: Set to the monorepoRoot to ensure all files in the workspace are monitored.
    • resolver.nodeModulesPaths: Includes both the local projectRoot/node_modules and the monorepoRoot/node_modules to allow proper dependency resolution across the workspace.
    • resolver.unstable_enablePackageExports: Set to true to enable support for the exports field in package.json.
    const { getDefaultConfig } = require('expo/metro-config');
    const path = require('node:path');
    
    const projectRoot = __dirname;
    const monorepoRoot = path.resolve(projectRoot, '../..');
    
    const config = getDefaultConfig(projectRoot);
    
    config.watchFolders = [monorepoRoot];
    config.resolver.nodeModulesPaths = [
      path.resolve(projectRoot, 'node_modules'),
      path.resolve(monorepoRoot, 'node_modules'),
    ];
    
    config.resolver.unstable_enablePackageExports = true;
    
    module.exports = config;
  5. Configure typegpu-confetti with default props

    main

    When using typegpu-confetti, you can provide a configuration object of type ConfettiProps. If you do not provide certain properties, the following default values are used:

    • maxDurationTime: The maximum duration of the confetti effect in seconds (default: 2).
    • initParticleAmount: The number of particles initialized at the start (default: 200).
    • maxParticleAmount: The maximum number of particles allowed in the simulation (default: 1000).
    • size: The scale factor for particles (default: 1).
    • colorPalette: An array of RGBA colors represented as [r, g, b, a] arrays (default: a specific palette of 5 colors).
    • gravity: A function returning a 2D vector (d.vec2f) representing gravity direction and strength. The default is d.vec2f(0, -0.3).
    • initParticle: A function used to initialize each particle's position and velocity. The default uses randf.sample() to distribute particles across a specific range.
    // Example of the shape of the default configuration object
    const defaults = {
      maxDurationTime: 2,
      initParticleAmount: 200,
      maxParticleAmount: 1000,
      size: 1,
      colorPalette: [
        [154, 177, 155, 1],
        [67, 129, 193, 1],
        [99, 71, 77, 1],
        [239, 121, 138, 1],
        [255, 166, 48, 1],
      ],
      gravity: () => {
        'use gpu';
        return d.vec2f(0, -0.3);
      },
      initParticle: (i) => {
        'use gpu';
        // ... initialization logic
      },
    };
  6. Configure Confetti via Props

    main

    The Confetti component accepts several props to customize the animation. Note: Changing any prop will restart the animation.

    PropTypeDescription
    colorPalette[number, number, number, number][]Array of [r, g, b, a] colors for random assignment.
    sizenumberMultiplier for particle size (< 1 smaller, > 1 bigger).
    maxDurationTimenumber | nullAnimation duration in seconds. Particles fade out 1s before this time.
    initParticleAmountnumberParticles drawn on mount. Set to 0 to prevent auto-start.
    maxParticleAmountnumberMax particles allowed. If exceeded, oldest particles are replaced instantly.
    gravityGravityFnGPU function for particle acceleration. Requires "use gpu" directive.
    initParticleInitParticleFnGPU function for initializing particle state. Requires "use gpu" directive.
    styleStyleProp<ViewStyle>Overrides default styling on the inner Canvas.

    Custom GPU Functions: To use gravity or initParticle, you must mark them with the "use gpu" directive so unplugin-typegpu can transpile them.

    GravityFn signature:

    (args: { pos: d.v2f }) => d.v2f

    InitParticleFn signature:

    (args: { index: number }) => void

    Inside initParticle, you can access _particles (a TgpuArray of ParticleData) and _maxDurationTime via TypeGPU accessors.

  7. Trigger confetti using the useConfetti hook

    main

    The useConfetti hook allows you to access the confetti controller from any component nested within a ConfettiProvider.

    Calling useConfetti() returns a RefObject<ConfettiRef | null>. To trigger the confetti effect, you must access the .current property of the returned ref and call its methods (defined in ConfettiRef).

    Note: Since the hook returns a ref that might be null initially, always check if confettiRef.current exists before calling methods on it.

    import { useConfetti } from 'typegpu-confetti/react-native';
    
    function MyComponent() {
      const confettiRef = useConfetti();
    
      const handlePress = () => {
        // Access the ref via .current to call confetti methods
        confettiRef?.current?.burst();
      };
    
      return (
        <Button title="Celebrate!" onPress={handlePress} />
      );
    }
  8. Set up confetti with ConfettiProvider

    main

    To use confetti in a React Native application, wrap your component tree (or a specific view) with the ConfettiProvider. This provider manages the confetti state and provides a context that allows you to trigger confetti effects from any child component using the useConfetti hook.

    ConfettiProvider accepts standard ConfettiProps (such as initParticleAmount and maxParticleAmount) and an optional style prop for the container view.

    import { ConfettiProvider } from 'typegpu-confetti/react-native';
    
    function App() {
      return (
        <ConfettiProvider initParticleAmount={10} maxParticleAmount={100}>
          <MyComponent />
        </ConfettiProvider>
      );
    }
  9. Access GPU buffers with `tgpu.accessor`

    main

    Accessors provide a way to read from or write to GPU-resident data (like buffers or textures) from within your GPU functions. Accessors are typically used for arrays of data or single scalar values.

    Common patterns include:

    • tgpu.accessor(d.f32) for single values like time or deltaTime.
    • tgpu.accessor(d.arrayOf(Type, size)) for arrays of structs like particlesAccess.

    To access the value in a GPU function, use the .$ property (e.g., particlesAccess.$[index]).

    export const particlesAccess = tgpu.accessor(d.arrayOf(ParticleData, 1));
    export const deltaTime = tgpu.accessor(d.f32); 
    export const particlesAccess = tgpu.accessor(d.arrayOf(ParticleData, 1));
    export const deltaTime = tgpu.accessor(d.f32);
  10. Create GPU functions with `tgpu.fn`, `tgpu.vertexFn`, `tgpu.fragmentFn`, and `tgpu.computeFn`

    main

    typegpu provides specialized functions for different pipeline stages:

    • tgpu.fn: A general-purpose GPU function with defined input/output types.
    • tgpu.vertexFn: A vertex shader function. It takes an in object (defining inputs) and an out object (defining the VertexOutput).
    • tgpu.fragmentFn: A fragment shader function. It takes an in object (the interpolated vertex output) and an out object (the color/output type).
    • tgpu.computeFn: A compute shader function. It takes an in object (like globalInvocationId) and allows specifying a workgroupSize. Use . $uses({...}) to declare dependencies on slots or accessors.

    Example of a compute function:

    export const mainCompute = tgpu.computeFn({
      in: { gid: d.builtin.globalInvocationId },
      workgroupSize: [64],
    })(/* wgsl */ `...`).$uses({ gravity, particles: particlesAccess, deltaTime, time });
    export const mainCompute = tgpu
      .computeFn({
        in: { gid: d.builtin.globalInvocationId },
        workgroupSize: [64],
      })(/* wgsl */ `...`)
      .$uses({ gravity, particles: particlesAccess, deltaTime, time });
  11. Use the Confetti component with ConfettiProps

    main

    The typegpu-confetti package provides a Confetti component. To configure its behavior, use the ConfettiProps type. Note that several older property names have been deprecated in favor of new slot and accessor names to improve clarity and consistency within the core schemas.

    import { type ConfettiProps } from 'typegpu-confetti';
    
    // Example usage of the props type
    const props: ConfettiProps = {
      // ... configuration options
    };
  12. Define GPU data structures with `d.struct`

    main

    Use d.struct to define data layouts that will be shared between CPU and GPU. This is essential for defining particle properties or vertex attributes. You can use built-in types like d.f32, d.vec2f, d.vec4f, and d.u32 within these structures.

    Example of defining particle data and geometry:

    export const ParticleGeometry = d.struct({
      tilt: d.f32,
      angle: d.f32,
      color: d.vec4f,
    });
    
    export const ParticleData = d.struct({
      position: d.vec2f,
      velocity: d.vec2f,
      seed: d.f32,
      timeLeft: d.f32,
    });