npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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-frames

Usage

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 begin
  • afterStop — recording stopped
  • newZip(zip) — a fresh JSZip was created (on construct, and after each download/cancel)
  • beforeDownload(zip) — last chance to add files to the zip
  • afterDownload

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.mp4

Frame 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