Rive Canvasv0.1.9Documentation 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-canvasOverview
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.
With Play/Pause Control
Interactive player with ref-based programmatic control.
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
- Check Connection: Detects
navigator.connection.effectiveType - Compare Speed: Compares against
minimumEffectiveTypeprop - Load or Fallback:
- Fast enough → Load Rive animation
- Too slow → Show
childrenfallback content
- 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
childrenfor 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-motionmedia 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.
Related Components
- Image - For static images with optimization
- Video - For video content
- Skeleton - For loading states
- Spinner - For simple loading indicators
External Resources
Radio Cards
A deprecated radio group component with card-style visual presentation. Use RadioGroup component for new implementations with better accessibility and simpler API.
SubText
Renders an accessible subtext component for displaying helper text, descriptions, and validation messages associated with form controls and other UI elements.