-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(node-support): 🎸 Enable playing dotLottie animations in non-brow…
…ser environments (#70) * feat: 🎸 add resize method and renderConfig * chore: 🤖 add changelog * feat: 🎸 ability to run animations on non-browser environment * chore: 🤖 update tsup target to neutral * chore: 🤖 dotLottie to gif node example * chore: 🤖 update pnpm-lock file * chore: 🤖 add changelog * chore: 🤖 update changelog * chore: 🤖 remove unused utils bounce func * chore: 🤖 apply suggested changes * chore: 🤖 improve example * chore: 🤖 update README
- Loading branch information
Showing
16 changed files
with
499 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@lottiefiles/dotlottie-web': minor | ||
--- | ||
|
||
feat(node-support): 🎸 Enable playing dotLottie animations in non-browser environments |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,3 +6,4 @@ | |
*.json linguist-language=JSON-with-Comments | ||
|
||
*.wasm binary | ||
*.gif binary |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
output |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# DotLottie Web Node.js Example | ||
|
||
## Overview | ||
|
||
This example demonstrates how to use the DotLottie Web package in a Node.js environment. It showcases controlling animation playback, rendering frame by frame, and converting a Lottie animation into a GIF file. | ||
|
||
## How to Run | ||
|
||
To run the example with default settings, execute: | ||
|
||
```bash | ||
pnpm start | ||
``` | ||
|
||
The output GIF will be saved to `./output/animation.gif`. | ||
|
||
 | ||
|
||
To customize settings and explore different options, use the command line arguments as follows: | ||
|
||
```bash | ||
pnpm start --width [width] --height [height] --fps [fps] --repeat [repeat] --quality [quality] --input [input file or URL] --speed [speed] | ||
``` | ||
|
||
### Arguments | ||
|
||
* `--width`: Width of the output GIF (default: 200) | ||
* `--height`: Height of the output GIF (default: 200) | ||
* `--fps`: Frames per second for the output GIF (default: 60) | ||
* `--repeat`: Number of times the GIF should repeat (default: 0 for infinite) | ||
* `--input`: Path or URL to the Lottie animation file (default: example animation) | ||
* `--speed`: Speed multiplier for the animation (default: 1) | ||
* `--quality`: Quality of the output GIF ('high', 'mid', or 'low'; default: 'mid') | ||
|
||
### Example Command | ||
|
||
```bash | ||
pnpm start --width 200 --height 200 --fps 30 --repeat 0 --quality high --input https://lottie.host/aaccfd1e-487e-4e9a-9d20-c57299089cfc/iVNpuLw0co.lottie --speed 1.5 | ||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/** | ||
* Copyright 2023 Design Barn Inc. | ||
*/ | ||
|
||
import fs from 'node:fs'; | ||
import path from 'node:path'; | ||
|
||
import type { Config } from '@lottiefiles/dotlottie-web'; | ||
import { DotLottie } from '@lottiefiles/dotlottie-web'; | ||
import { createCanvas } from '@napi-rs/canvas'; | ||
import GIFEncoder from 'gif-encoder'; | ||
import minimist from 'minimist'; | ||
|
||
const wasmBase64 = fs.readFileSync('./node_modules/@lottiefiles/dotlottie-web/dist/renderer.wasm').toString('base64'); | ||
const wasmDataUri = `data:application/octet-stream;base64,${wasmBase64}`; | ||
|
||
// This is only required for testing the local version of the renderer | ||
DotLottie.setWasmUrl(wasmDataUri); | ||
|
||
const rawArgs = minimist(process.argv.slice(2)); | ||
|
||
interface Args { | ||
fps: number; | ||
height: number; | ||
input: string; | ||
quality: 'high' | 'mid' | 'low'; | ||
repeat: number; | ||
speed: number; | ||
width: number; | ||
} | ||
|
||
const qualityMap = { | ||
high: 1, | ||
low: 20, | ||
mid: 10, | ||
}; | ||
|
||
const args: Args = { | ||
width: Number(rawArgs['width']) || 200, | ||
height: Number(rawArgs['height']) || 200, | ||
fps: Number(rawArgs['fps']) || 60, | ||
repeat: Number(rawArgs['repeat']) || 0, | ||
input: rawArgs['input'] || 'https://lottie.host/195549aa-ad39-4c51-80ee-a8899c2264ee/cWdgpn8n7B.lottie', | ||
speed: Number(rawArgs['speed']) || 1, | ||
quality: rawArgs['quality'] || 'mid', | ||
}; | ||
|
||
if (!args.input) { | ||
console.error('No input file provided. Use --input to specify the Lottie animation file.'); | ||
process.exit(1); | ||
} | ||
|
||
const canvas = createCanvas(args.width, args.height); | ||
const ctx = canvas.getContext('2d'); | ||
|
||
const dotLottieConfig: Config = { | ||
speed: args.speed, | ||
canvas: canvas as unknown as HTMLCanvasElement, | ||
}; | ||
|
||
if (args.input.startsWith('http://') || args.input.startsWith('https://')) { | ||
dotLottieConfig.src = args.input; | ||
} else { | ||
const filePath = path.resolve(args.input); | ||
|
||
if (!fs.existsSync(filePath)) { | ||
console.error(`File not found: ${filePath}`); | ||
process.exit(1); | ||
} | ||
|
||
dotLottieConfig.data = fs.readFileSync(filePath, 'utf-8'); | ||
} | ||
|
||
const gif = new GIFEncoder(args.width, args.height); | ||
|
||
const dotLottie = new DotLottie(dotLottieConfig); | ||
|
||
const outputPath = path.resolve('./output'); | ||
|
||
dotLottie.addEventListener('load', () => { | ||
if (!fs.existsSync(outputPath)) { | ||
fs.mkdirSync(outputPath); | ||
} | ||
|
||
const file = fs.createWriteStream(path.resolve(outputPath, 'animation.gif')); | ||
|
||
gif.pipe(file); | ||
|
||
gif.setRepeat(args.repeat); | ||
gif.setFrameRate(args.fps); | ||
gif.setQuality(qualityMap[args.quality]); | ||
|
||
gif.writeHeader(); | ||
|
||
const frameRateAdjustedForSpeed = args.fps / dotLottie.speed; | ||
const delay = 1 / frameRateAdjustedForSpeed; | ||
|
||
console.info( | ||
` | ||
Recording GIF with the following settings: | ||
- Width: ${args.width} | ||
- Height: ${args.height} | ||
- FPS: ${args.fps} | ||
- Repeat: ${args.repeat} | ||
- Animation Speed: ${args.speed} | ||
- Quality: ${args.quality} | ||
- Delay: ${delay} | ||
- Total Frames: ${dotLottie.totalFrames} | ||
- Duration: ${dotLottie.duration} | ||
- Output Path: ${outputPath} | ||
`, | ||
); | ||
|
||
for (let timeElapsed = 0; timeElapsed <= dotLottie.duration; timeElapsed += delay) { | ||
const frameNo = (dotLottie.totalFrames - 1) * (timeElapsed / dotLottie.duration); | ||
|
||
dotLottie.setFrame(frameNo); | ||
} | ||
}); | ||
|
||
dotLottie.addEventListener('frame', (event) => { | ||
const frame = ctx.getImageData(0, 0, args.width, args.height).data; | ||
|
||
if (Math.round(event.currentFrame) >= dotLottie.totalFrames - 1) { | ||
console.log('Finished recording GIF'); | ||
gif.finish(); | ||
} else { | ||
gif.addFrame(frame); | ||
gif._read(1); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "@lottiefiles/dotlottie-web-node-example", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"license": "MIT", | ||
"scripts": { | ||
"build": "tsc --noEmit", | ||
"start": "tsx index.ts" | ||
}, | ||
"dependencies": { | ||
"@lottiefiles/dotlottie-web": "workspace:*", | ||
"@napi-rs/canvas": "^0.1.44", | ||
"gif-encoder": "^0.7.2", | ||
"minimist": "^1.2.8" | ||
}, | ||
"devDependencies": { | ||
"@types/gif-encoder": "^0.7.4", | ||
"@types/minimist": "^1.2.5", | ||
"@types/node": "^20.10.2", | ||
"tsx": "^4.6.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
// Extend from the build config | ||
"extends": "../../tsconfig.build.json", | ||
|
||
// Compiler options | ||
"compilerOptions": { | ||
// Source root directory | ||
"rootDir": ".", | ||
|
||
"resolveJsonModule": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/** | ||
* Copyright 2023 Design Barn Inc. | ||
*/ | ||
|
||
/* eslint-disable max-classes-per-file */ | ||
|
||
import { IS_BROWSER } from './constants'; | ||
|
||
interface AnimationFrameStrategy { | ||
cancelAnimationFrame(id: number): void; | ||
requestAnimationFrame(callback: (time: number) => void): number; | ||
} | ||
|
||
class WebAnimationFrameStrategy implements AnimationFrameStrategy { | ||
public requestAnimationFrame(callback: (time: number) => void): number { | ||
return window.requestAnimationFrame(callback); | ||
} | ||
|
||
public cancelAnimationFrame(id: number): void { | ||
window.cancelAnimationFrame(id); | ||
} | ||
} | ||
|
||
class NodeAnimationFrameStrategy implements AnimationFrameStrategy { | ||
private _lastHandleId: number = 0; | ||
|
||
private _lastImmediate: NodeJS.Immediate | null = null; | ||
|
||
public requestAnimationFrame(callback: (time: number) => void): number { | ||
if (this._lastHandleId >= Number.MAX_SAFE_INTEGER) { | ||
this._lastHandleId = 0; | ||
} | ||
|
||
this._lastHandleId += 1; | ||
|
||
this._lastImmediate = setImmediate(() => { | ||
callback(Date.now()); | ||
}); | ||
|
||
return this._lastHandleId; | ||
} | ||
|
||
public cancelAnimationFrame(_id: number): void { | ||
if (this._lastImmediate) { | ||
clearImmediate(this._lastImmediate); | ||
} | ||
} | ||
} | ||
|
||
export class AnimationFrameManager { | ||
private readonly _strategy: AnimationFrameStrategy; | ||
|
||
public constructor() { | ||
this._strategy = IS_BROWSER ? new WebAnimationFrameStrategy() : new NodeAnimationFrameStrategy(); | ||
} | ||
|
||
public requestAnimationFrame(callback: (time: number) => void): number { | ||
return this._strategy.requestAnimationFrame(callback); | ||
} | ||
|
||
public cancelAnimationFrame(id: number): void { | ||
this._strategy.cancelAnimationFrame(id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/** | ||
* Copyright 2023 Design Barn Inc. | ||
*/ | ||
|
||
export const IS_BROWSER = typeof window === 'object'; | ||
export const MS_TO_SEC_FACTOR = 1000; |
Oops, something went wrong.