canvas-frames
v0.1.1
Published
record a canvas animation frame by frame and download results to a zip
Readme
canvas-frames
Record a canvas animation frame by frame and download the frames as a zip.
Instead of capturing in real time, it steps your draw function forward by a fixed
time step (1000 / fps) and grabs the canvas after each step. Every frame gets
rendered no matter how slow the drawing is, so the exported sequence plays back at
exactly the fps you asked for. Feed the frames to ffmpeg (or whatever) to make a
video or gif.
Comes with a small floating control panel for start / stop / cancel, max frames, and fps.
Install
pnpm add canvas-frames
# or
npm install canvas-framesUsage
Your draw function must take a time (in milliseconds) and render that moment
deterministically. If it advances state internally (for example by accumulating deltas or reading
performance.now()), the recording won't line up.
The ms time provided is artificial! The snaps of each frame in your browser will not necessarily happen at your provided fps. The intention is that you will use these frames later to create a video using that fps value.
import { Recorder } from 'canvas-frames'
const canvas = document.querySelector('canvas')!
const ctx = canvas.getContext('2d')!
const draw = (t: number) => {
const seconds = t / 1000
ctx.clearRect(0, 0, canvas.width, canvas.height)
// ...draw the frame at `seconds`
}
// normal playback
let rafId = requestAnimationFrame(function loop(t) {
draw(t)
rafId = requestAnimationFrame(loop)
})
const recorder = new Recorder({ canvas, draw })
// stop your own loop so the recorder is the only thing driving `draw`
recorder.on('beforeStart', () => cancelAnimationFrame(rafId))
// optional: adds the floating control panel to the page
recorder.addControls()Hit Start in the panel. When it reaches max frames (or you hit Stop) it zips the images and downloads them.
addControls() is optional; you can skip it and call recorder.start() /
stop() / cancel() yourself if preferred.
Without a bundler
Load it via <script> tags - requires jszip first, then canvas-frames:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/canvas-frames/dist/canvas-frames.umd.js"></script>
<script>
const canvas = document.querySelector('canvas')
const recorder = new canvasFrames.Recorder({ canvas, draw })
</script>Options
| option | default | |
| ----------- | ---------- | --------------------------------------------------------------------- |
| canvas | required | the canvas to capture |
| draw | required | (t: number) => void, t in ms |
| fileName | 'images' | name of the downloaded zip |
| reset | — | called after a recording finishes or is canceled |
| imageType | 'png' | 'png' or 'jpg' |
| fps | 60 | frames per second |
| metadata | — | (recorder) => object \| null, written to metadata.json in the zip |
Max frames (default 1000) and fps are also editable in the panel.
addControls(position?) takes the corner to place the panel in:
'top-left' (default), 'top-right', 'bottom-left', or 'bottom-right'.
Events
Recorder is an event emitter — subscribe with recorder.on(name, handler):
beforeStart— recording is about to beginafterStop— recording stoppednewZip(zip)— a freshJSZipwas created (on construct, and after each download/cancel)beforeDownload(zip)— last chance to add files to the zipafterDownload
API
start(), stop(), cancel(), download(), stopAndDownload(), destroy() —
plus isRecording, currentCount, maxFrames, and status.
addControls(position?) builds the floating panel, wires it up to the recorder,
and appends it to document.body. removeControls() tears it back down. Neither
is required — the recorder works fully from script without a panel.
destroy() stops any recording in progress and calls removeControls().
Making a video from the frames
ffmpeg -framerate 60 -i image-%04d.png -c:v libx264 -pix_fmt yuv420p out.mp4Frame numbers are zero-padded to the width of maxFrames, so adjust %04d if
you record a different number of frames.
Development
pnpm dev # vite dev server, runs src/example.ts
pnpm build # tsc + vite build -> dist/
pnpm preview