@la-moore/gif
v1.0.0
Published
TypeScript library for working with GIFs in the browser.
Readme
@la-moore/gif
TypeScript library for working with GIFs in the browser:
- Encoding (creating) animations with
GifWriter. - Decoding (reading) GIFs with
GifReader.
The encoder is optimized for browser usage: the heavy work (quantization/palette) is performed in a Web Worker so it doesn’t block the UI.
Features
- Create GIFs from
HTMLCanvasElement/HTMLImageElement. - Create GIFs from
ImageData(when frames are already prepared). - Render progress (
onRenderProgress). - Output as
Blob(getBlobGIF) or Data URL (base64) (getBase64GIF). - Frame delay control (milliseconds) and
repeat(loop count). - Dithering / palette / transparency / disposal (globally and per-frame).
- Playback modes:
loop(default)reversebounce
- Read GIFs: load from
Uint8Array/ArrayBuffer/Blob, iterate frames with RGBA decode.
Benefits
- Doesn’t block the main thread while preparing frames (worker pool).
- Simple API: add frames -> get a
Blob/base64. - TypeScript types included.
- No DOM framework dependency (React/Vue not required).
Installation
npm i @la-moore/gifor
pnpm add @la-moore/gifor
yarn add @la-moore/gifQuick start (create a GIF)
import { GifWriter } from '@la-moore/gif'
const gif = new GifWriter({
width: 320,
height: 240,
numWorkers: 2,
})
gif.setDelay(250) // milliseconds
gif.setRepeat(0) // 0 = loop forever
gif.addFrame(canvas) // HTMLCanvasElement or HTMLImageElement
gif.getBlobGIF((blob) => {
const url = URL.createObjectURL(blob)
// e.g. img.src = url
})Render progress
const gif = new GifWriter({ width: 320, height: 240 })
gif.onRenderProgress((p) => {
// p: 0..1
console.log('progress', Math.round(p * 100) + '%')
})
gif.addFrame(canvas)
gif.getBlobGIF((blob) => {
console.log('done', blob.size)
})Playback modes (loop / reverse / bounce / once)
const gif = new GifWriter({ width: 320, height: 240, playbackMode: 'bounce' })
// you can change it later
gif.setPlaybackMode('reverse')Note: the mode affects the frame order/generation before rendering (e.g. bounce adds a reverse pass without duplicating the first/last frame).
Add a frame with per-frame options
gif.addFrame(canvas, {
delay: 120,
disposal: 2,
transparencyCutOff: 0.7,
// palette / dithering can also be set here
})Add a frame as ImageData
If you generate frames yourself (e.g. from WebGL/OffscreenCanvas) and already have ImageData:
gif.addFrameImageData(imageData, { delay: 80 })Get base64 (Data URL)
gif.getBase64GIF((dataUrl) => {
// data:image/gif;base64,...
img.src = dataUrl
})Cleanup and resource management
reset()clears frames and resets render state (the instance remains usable).destroy()terminates workers. After calling it, you should not keep using the instance.
gif.reset()
gif.destroy()Read a GIF (GifReader)
import { GifReader } from '@la-moore/gif'
const res = await fetch('/example.gif')
const buffer = await res.arrayBuffer()
const reader = new GifReader()
reader.loadArrayBuffer(buffer)
console.log(reader.width, reader.height)
console.log('frames', reader.framesCount)
console.log('duration (centiseconds)', reader.duration)
reader.forEach((frame, i, rgba) => {
// frame: frame metadata (omggif)
// rgba: Uint8ClampedArray, size width*height*4
console.log('frame', i, frame)
})You can also load from a Blob:
const reader = new GifReader()
await reader.loadBlob(fileBlob)Notes on Web Worker and bundlers
Inside the library, the worker is created like this:
new Worker(new URL('./gif.worker.js', import.meta.url), { type: 'module' })That means:
- Vite automatically detects the worker and bundles it as a separate chunk.
- For Webpack/Next.js, make sure your build supports
new URL(..., import.meta.url)for workers (in modern versions this is usually fine). If you use SSR, import and createGifWriteronly on the client because it relies ondocument,canvas, andWorker.
Build (for maintainers)
npm run build