Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/add-webgl-webgpu-react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@lottiefiles/dotlottie-react": minor
---

Add WebGL and WebGPU renderer support via subpath exports

* `@lottiefiles/dotlottie-react/webgl` — WebGL renderer component
* `@lottiefiles/dotlottie-react/webgpu` — WebGPU renderer component (with optional `device` prop)
16 changes: 16 additions & 0 deletions examples/next/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config) => {
// Deduplicate React in the monorepo so linked workspace packages
// resolve the same copy as the app (prevents "invalid hook call").
config.resolve.alias = {
...config.resolve.alias,
react: path.resolve(__dirname, 'node_modules/react'),
'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
};

return config;
},
};

export default nextConfig;
10 changes: 0 additions & 10 deletions examples/next/src/pages/api/hello.ts

This file was deleted.

163 changes: 102 additions & 61 deletions examples/next/src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,78 +1,119 @@
import type { DotLottie, DotLottieWorker } from '@lottiefiles/dotlottie-react';
import type { DotLottie, DotLottieReactProps } from '@lottiefiles/dotlottie-react';
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
import { Inter } from 'next/font/google';
import dynamic from 'next/dynamic';
import Head from 'next/head';
import { useState } from 'react';
import type { ComponentType } from 'react';
import { useEffect, useState } from 'react';

import styles from '@/styles/Home.module.css';
type Renderer = 'canvas' | 'webgl' | 'webgpu';

const inter = Inter({ subsets: ['latin'] });
const DotLottieWebGL = dynamic(() => import('@lottiefiles/dotlottie-react/webgl').then((m) => m.DotLottieReact), {
ssr: false,
});
const DotLottieWebGPU = dynamic(() => import('@lottiefiles/dotlottie-react/webgpu').then((m) => m.DotLottieReact), {
ssr: false,
});

const src = 'https://lottie.host/e641272e-039b-4612-96de-138acfbede6e/bc0sW78EeR.lottie';
const rendererComponent: Record<Renderer, ComponentType<DotLottieReactProps>> = {
canvas: DotLottieReact,
webgl: DotLottieWebGL,
webgpu: DotLottieWebGPU,
};

const animations = [
'https://lottie.host/e641272e-039b-4612-96de-138acfbede6e/bc0sW78EeR.lottie',
'https://lottie.host/f315768c-a29b-41fd-b5a8-a1c1dfb36cd2/CRiiNg8fqQ.lottie',
'https://lottie.host/647eb023-6040-4b60-a275-e2546994dd7f/zDCfp5lhLe.json',
];

export default function Home() {
const [dotLottie, setDotLottie] = useState<DotLottie | DotLottieWorker | null>(null);
const [showDotLottie, setShowDotLottie] = useState(false);
const [dotLottie, setDotLottie] = useState<DotLottie | null>(null);
const [renderer, setRenderer] = useState<Renderer>('canvas');
const [loop, setLoop] = useState(true);
const [speed, setSpeed] = useState(1);
const [currentFrame, setCurrentFrame] = useState(0);
const [srcIdx, setSrcIdx] = useState(0);
const [autoplay, setAutoplay] = useState(true);

useEffect(() => {
function onFrame(event: { currentFrame: number }) {
setCurrentFrame(event.currentFrame);
}

dotLottie?.addEventListener('frame', onFrame);

return () => {
dotLottie?.removeEventListener('frame', onFrame);
};
}, [dotLottie]);

const progress = dotLottie?.isLoaded ? (currentFrame / dotLottie.totalFrames) * 100 : 0;
const Component = rendererComponent[renderer];

return (
<>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<title>dotlottie-react Next.js Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={`${styles.main} ${inter.className}`}>
{showDotLottie && (
<DotLottieReact
dotLottieRefCallback={setDotLottie}
style={{
minWidth: '100px',
}}
src={src}
loop
autoplay
renderConfig={{
autoResize: true,
}}
/>
)}
<div>
<button
onClick={(): void => {
setShowDotLottie(!showDotLottie);
}}
>
{showDotLottie ? 'Hide' : 'Show'}
</button>
<button
onClick={(): void => {
if (dotLottie) {
dotLottie.play();
}
}}
>
Play
</button>
<button
onClick={(): void => {
if (dotLottie) {
dotLottie.pause();
}
}}
>
Pause
</button>
<button
onClick={(): void => {
if (dotLottie) {
dotLottie.stop();
}
}}
>
Stop
</button>
<main style={{ maxWidth: 720, margin: '0 auto', padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
<h2 style={{ marginBottom: '1rem' }}>dotlottie-react + Next.js</h2>

{/* Renderer selector */}
<fieldset style={{ marginBottom: '1rem', padding: '0.75rem', border: '1px solid #ccc', borderRadius: 8 }}>
<legend>Renderer</legend>
<div style={{ display: 'flex', gap: '1rem' }}>
{(['canvas', 'webgl', 'webgpu'] as const).map((r) => (
<label key={r} style={{ cursor: 'pointer' }}>
<input
type="radio"
name="renderer"
value={r}
checked={renderer === r}
onChange={() => setRenderer(r)}
/>{' '}
{r === 'canvas' ? 'Canvas 2D' : r.toUpperCase()}
</label>
))}
</div>
</fieldset>

{/* Animation */}
<Component
key={renderer}
dotLottieRefCallback={setDotLottie}
src={animations[srcIdx]}
autoplay={autoplay}
loop={loop}
speed={speed}
renderConfig={{ autoResize: true }}
style={{ height: 400, border: '1px solid #ddd', borderRadius: 8 }}
/>

{/* Progress */}
<input
type="range"
min={0}
max={100}
value={progress}
readOnly
style={{ width: '100%', marginTop: '0.5rem' }}
/>

{/* Controls */}
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem', marginTop: '1rem' }}>
<button onClick={() => dotLottie?.play()}>Play</button>
<button onClick={() => dotLottie?.pause()}>Pause</button>
<button onClick={() => dotLottie?.stop()}>Stop</button>
<button onClick={() => setLoop((l) => !l)}>{loop ? 'Loop: ON' : 'Loop: OFF'}</button>
<button onClick={() => setAutoplay((a) => !a)}>{autoplay ? 'Autoplay: ON' : 'Autoplay: OFF'}</button>
<button onClick={() => setSpeed((s) => Math.round((s + 0.5) * 10) / 10)}>Speed +</button>
<button onClick={() => setSpeed((s) => Math.max(0.5, Math.round((s - 0.5) * 10) / 10))}>Speed -</button>
<button onClick={() => setSrcIdx((i) => (i + 1) % animations.length)}>Next animation</button>
</div>
<p style={{ marginTop: '0.5rem', fontSize: '0.85rem', color: '#888' }}>
Speed: {speed}x &middot; Frame: {Math.round(currentFrame)} &middot; Renderer: {renderer}
</p>
</main>
</>
);
Expand Down
Loading
Loading