Content

Rive Canvas
v0.1.9
Documentation Under Review

A performance-optimized React component for rendering Rive animations with network-aware loading, fallback support, and accessibility features.

Installation

Install the RiveCanvas component from the Catalyst component library:

npm install @pmi/catalyst-rive-canvas

Overview

The RiveCanvas component provides a performance-optimized way to embed Rive animations in React applications. It includes network-aware loading (won't load on slow connections), React Suspense integration, fallback content support, and built-in accessibility features for ARIA live regions.

Key Features

  • Network-Aware Loading: Detects connection speed and only loads on fast-enough networks
  • React Suspense Integration: Shows fallback content while loading
  • Fallback Support: Display alternative content for slow connections
  • Accessibility: ARIA live regions for screen readers
  • Lazy Loading: Rive script loaded on-demand via React.lazy
  • Programmatic Control: Ref-based API for play/pause control
  • Flexible Layout: Configurable fit and alignment options
  • Performance Optimized: Minimal bundle impact with code splitting

Examples

Basic Usage

Simple Rive animation with default settings.

Preview

With Play/Pause Control

Interactive player with ref-based programmatic control.

Preview

API Reference

RiveCanvas

The main component for rendering Rive animations with performance optimizations.

Prop

Type

LayoutOptions

Configuration for how the animation fits within its container.

Prop

Type

Rive Instance (Ref)

When using a ref, you get access to the Rive instance with these methods:

Prop

Type

Network-Aware Loading

The RiveCanvas component uses the Network Information API to detect connection speed and optimize performance.

Connection Speed Thresholds

Prop

Type

How It Works

  1. Check Connection: Detects navigator.connection.effectiveType
  2. Compare Speed: Compares against minimumEffectiveType prop
  3. Load or Fallback:
    • Fast enough → Load Rive animation
    • Too slow → Show children fallback content
  4. Adaptive: Listens for connection changes and updates accordingly

Fallback Strategy

<RiveCanvas
  src="/animation.riv"
  minimumEffectiveType="3g"
  fallback={<LoadingSpinner />} // Shown while script loads
>
  {/* Shown if connection is slower than 3g */}
  <StaticImage src="/fallback.png" />
</RiveCanvas>

Layout Options

Fit Values

How the animation should fit within the canvas:

  • fitWidth: Scales to fit canvas width (maintains aspect ratio)
  • fitHeight: Scales to fit canvas height (maintains aspect ratio)
  • cover: Covers entire canvas (may crop)
  • contain: Fits entirely within canvas (may have empty space)
  • fill: Stretches to fill canvas (may distort)
  • none: No scaling
  • scaleDown: Like contain but won't scale up

Alignment Values

Where the animation should be positioned:

  • center: Centered both horizontally and vertically (default)
  • topLeft, topCenter, topRight: Top edge alignment
  • centerLeft, centerRight: Middle edge alignment
  • bottomLeft, bottomCenter, bottomRight: Bottom edge alignment

Examples

// Cover entire canvas
<RiveCanvas
  src="/animation.riv"
  layout={{ fit: 'cover', alignment: 'center' }}
/>

// Fit width, align to top
<RiveCanvas
  src="/animation.riv"
  layout={{ fit: 'fitWidth', alignment: 'topCenter' }}
/>

// Contain with bottom-right alignment
<RiveCanvas
  src="/animation.riv"
  layout={{ fit: 'contain', alignment: 'bottomRight' }}
/>

Programmatic Control

Use a ref to control the animation programmatically:

import { useRef } from 'react';
import { RiveCanvas, type Rive } from '@pmi/catalyst-rive-canvas';

function MyComponent() {
  const riveRef = useRef<Rive>(null);

  return (
    <>
      <RiveCanvas ref={riveRef} src="/animation.riv" autoplay />

      <button onClick={() => riveRef.current?.play()}>Play</button>
      <button onClick={() => riveRef.current?.pause()}>Pause</button>
      <button onClick={() => riveRef.current?.stop()}>Stop</button>
      <button onClick={() => riveRef.current?.reset()}>Reset</button>
    </>
  );
}

Rive Callbacks

Use riveParams to access animation lifecycle events:

<RiveCanvas
  src="/animation.riv"
  riveParams={{
    onLoad: () => console.log('Animation loaded'),
    onLoadError: () => console.error('Failed to load'),
    onPlay: () => console.log('Playing'),
    onPause: () => console.log('Paused'),
    onStop: () => console.log('Stopped'),
    onLoop: () => console.log('Loop completed'),
    onStateChange: (event) => console.log('State changed', event),
  }}
/>

Accessibility

The RiveCanvas component follows accessibility best practices for animated content.

ARIA Live Regions

When providing a description prop, the component creates an ARIA live region:

<RiveCanvas
  src="/animation.riv"
  description="PMI certifications slider, playing"
  role="img"
  aria-label="animation"
/>

// Renders:
// <canvas role="img" aria-label="animation" aria-describedby="unique-id" />
// <p className="sr-only" id="unique-id" aria-live="assertive">
//   PMI certifications slider, playing
// </p>

Keyboard Accessibility

Make animations keyboard accessible with tabIndex:

<RiveCanvas
  src="/animation.riv"
  tabIndex={0}
  onClick={togglePlayPause}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      togglePlayPause();
    }
  }}
  description={`Animation ${isPlaying ? 'playing' : 'paused'}`}
  className="cursor-pointer focus:outline-2 focus:outline-blue-500"
/>

Reduced Motion

Respect user's motion preferences:

import { useEffect, useRef } from 'react';

function AccessibleRiveCanvas() {
  const riveRef = useRef<Rive>(null);

  useEffect(() => {
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

    if (prefersReducedMotion && riveRef.current) {
      riveRef.current.pause();
    }
  }, []);

  return (
    <RiveCanvas ref={riveRef} src="/animation.riv" autoplay>
      <StaticImage />
    </RiveCanvas>
  );
}

Best Practices

  • Always provide fallback content: Use children for slow connections
  • Use description prop: For screen reader context
  • Make interactive animations focusable: Add tabIndex={0}
  • Provide keyboard controls: Add Enter/Space key handlers for play/pause
  • Respect reduced motion: Check prefers-reduced-motion media query
  • Meaningful aria-label: Describe what the animation shows

Performance Optimization

Code Splitting

RiveCanvas uses React.lazy() to load the Rive library on-demand:

// Rive library is only loaded when RiveCanvas is rendered
const RiveCanvasBase = React.lazy(() => import('./RiveCanvasBase'));

Network-Aware Loading

By default, animations only load on 4G connections:

// Only loads on 4G or faster (default)
<RiveCanvas src="/animation.riv" minimumEffectiveType="4g" />

// More permissive - loads on 3G or faster
<RiveCanvas src="/animation.riv" minimumEffectiveType="3g" />

// Conservative - only loads on excellent connections
<RiveCanvas src="/animation.riv" minimumEffectiveType="4g" />

Suspense Integration

Use the fallback prop for loading states:

<RiveCanvas src="/animation.riv" fallback={<Skeleton className="h-64 w-full" />}>
  <StaticImage />
</RiveCanvas>

Asset Optimization

  • Optimize Rive files: Keep .riv files small (<500KB recommended)
  • Use CDN: Host .riv files on a CDN for faster loading
  • Compress assets: Rive files can be compressed
  • Lazy load: Only load animations when they enter viewport

Common Patterns

Hero Section Animation

<section className="hero">
  <RiveCanvas
    src="/animations/hero.riv"
    autoplay
    minimumEffectiveType="3g"
    layout={{ fit: 'cover', alignment: 'center' }}
    className="absolute inset-0 -z-10"
    description="Animated background graphics"
  >
    <div className="absolute inset-0 bg-gradient-to-r from-blue-500 to-purple-600" />
  </RiveCanvas>

  <div className="relative z-10">
    <h1>Welcome to Our Site</h1>
    <p>Experience interactive animations</p>
  </div>
</section>

Interactive Mascot

function InteractiveMascot() {
  const riveRef = useRef<Rive>(null);
  const [state, setState] = useState('idle');

  useEffect(() => {
    // Trigger different animations based on state
    // This requires specific Rive state machine setup
  }, [state]);

  return (
    <RiveCanvas
      ref={riveRef}
      src="/mascot.riv"
      autoplay
      onClick={() => setState('wave')}
      tabIndex={0}
      className="w-64 h-64 cursor-pointer"
      description={`Mascot is ${state}`}
    >
      <img src="/mascot-static.png" alt="Mascot" />
    </RiveCanvas>
  );
}

Loading Indicator

<RiveCanvas
  src="/spinner.riv"
  autoplay
  minimumEffectiveType="2g"
  layout={{ fit: 'contain', alignment: 'center' }}
  className="w-16 h-16"
  description="Loading content"
>
  <div className="w-16 h-16 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
</RiveCanvas>

Carousel/Slider Animation

<RiveCanvas
  src="/carousel.riv"
  autoplay
  layout={{ fit: 'fitWidth', alignment: 'center' }}
  className="w-full h-64"
  description="Animated carousel showing featured products"
  minimumEffectiveType="3g"
>
  <img src="/carousel-static.jpg" alt="Featured products" />
</RiveCanvas>

Troubleshooting

Animation Not Loading

Check connection speed and threshold:

// ✓ Correct - allows 3G and faster
<RiveCanvas
  src="/animation.riv"
  minimumEffectiveType="3g"
>
  <FallbackImage />
</RiveCanvas>

// ⚠ Warning - requires 4G (may not load on some devices)
<RiveCanvas
  src="/animation.riv"
  minimumEffectiveType="4g"
>
  <FallbackImage />
</RiveCanvas>

Fallback Not Showing

Ensure fallback content is provided in children:

// ✓ Correct - provides fallback
<RiveCanvas src="/animation.riv">
  <img src="/fallback.png" alt="Fallback" />
</RiveCanvas>

// ✗ Incorrect - no fallback for slow connections
<RiveCanvas src="/animation.riv" />

Ref Methods Not Working

Ensure animation is loaded before calling methods:

// ✓ Correct - wait for load
<RiveCanvas
  ref={riveRef}
  src="/animation.riv"
  riveParams={{
    onLoad: () => {
      // Now safe to call methods
      riveRef.current?.play();
    },
  }}
/>;

// ✗ Incorrect - may call before load
useEffect(() => {
  riveRef.current?.play(); // May be null
}, []);

Layout Issues

Ensure container has explicit dimensions:

// ✓ Correct - explicit height
<RiveCanvas
  src="/animation.riv"
  className="h-64 w-full"
/>

// ⚠ Warning - may have 0 height
<RiveCanvas
  src="/animation.riv"
  className="w-full"
/>

CORS Errors

Ensure .riv files are served with proper CORS headers:

# nginx example
location /rive/ {
  add_header Access-Control-Allow-Origin *;
}

Browser Support

  • Modern Browsers: Full support (Chrome, Firefox, Safari, Edge)
  • Network Information API:
    • Supported: Chrome, Edge, Opera
    • Limited: Firefox (via flag)
    • Not supported: Safari, iOS Safari
    • Fallback: Shows Suspense fallback when API unavailable

On browsers without Network Information API support, the component will show the Suspense fallback until manually loaded or default behavior occurs.

  • Image - For static images with optimization
  • Video - For video content
  • Skeleton - For loading states
  • Spinner - For simple loading indicators

External Resources

On this page