Best way to export an animation from a p5.js sketch in 2026

I have used a couple of methods which are good for sharing video renders on social media without worrying about high quality. But now I want the best quality possible. 4K, 30/60fps, and high bitrate mp4 to specific.

I have seen posts online using other methods which are good for high quality but they all seem very outdated. I’m wondering if people are still using these old methods or if something new has come along.

Something to keep in mind. I have a very old (15 year old) laptop. It has a first gen i7 at 1.6 GHz, old ATI 1GB GPU, and upgraded to 8GB Ram and an SSD. That’s why I can’t do real time rendering and have to do offline rendering so that each frame can take as long as needed.

I have used:

  • saveGif - Not good quality, and my laptop can barely handle it.
  • p5.capture - I have only used direct video export in real time. In this mode, for high quality means a lot of frames dropped on my laptop.
  • Screen recording software - Not possible to record anything bigger than 1600x900 on my laptop, and also a lot of dropped frames and low quality.

From what I see, the best way to do it is to render each frame one by one as a PNG sequence and then stitch them together with FFmpeg. The question is what to use to render the frames as PNGs.

CCapture.js seems to be the most talked about, but I see it hasn’t been updated in 8 years. Also, seems like the last p5 version that it worked with as intended was p5 v0.9.0, although there is a workaround apparently. Seeing that it was such a good solution but it has since been abandoned is what makes me wonder if there is a new better way that makes CCapture unnecessary now.

Maybe saveCanvas is that new way? I know there’s also saveFrames, but it has a limit of 15 seconds. Does saveCanvas have any limitations?

What about p5.capture PNG mode? This does offline rendering I believe. Is it a better solution than p5’s built in saveCanvas?

Any other methods that you guys use or know of?

And lastly what about converting p5 into Processing sketches? I know Processing will run better than p5 does in a browser, especially on my old laptop. How are the options to export high quality videos there? I have no experience with Processing or Java, but I guess an LLM should have no problem translating between the two. But would that be worth it?

Here are a few other options I can think of!

p5.record.js

Here’s another community library to try: GitHub - limzykenneth/p5.record.js: p5.js addon providing functions to record HTML canvas based sketches · GitHub

For recording as slowly as it takes to capture everything, there’s a manual frame capture mode where you call frame() when a frame is done, and you can still tell the video what the output frame rate should be.

Make a recording wrapper in something like Electron

In the past when I’ve done client work I’ve made an Electron project that runs my sketch instead of Chrome. This gives me the ability to save frames without them all going through the browser’s download manager, and lets me call out to other programs, such as ffmpeg, to have full control of mixing and quality, all within the one project instead of manually having to run commands after the downloads have finished.

Here’s some boilerplate I included in the end of my setup function inside of my Electron sketch:

if (doExport) {
  noLoop()

  const startExport = async () => {
    window.api.startSaving()

    for (let i = 0; i < totalFrames; i++) {
      frameCount = i
      redraw()
      await new Promise(res => requestAnimationFrame(res))
      window.api.saveFrame(i, _renderer.elt.toDataURL())
    }

    window.api.mix(fps)
  }

  startExport()
}

And then I have this Electron preload script that gets injected into the page to bridge between browser JS and node.js:

const renderer = createCanvas(1920, 1080)
pixelDensity(2)

// ...

if (doExport) {
  noLoop()

  const startExport = async () => {
    window.api.startSaving()

    for (let i = 0; i < totalFrames; i++) {
      frameCount = i
      redraw()
      await new Promise(res => requestAnimationFrame(res))
      window.api.saveFrame(i, renderer.elt.toDataURL())
    }

    window.api.mix(fps)
  }

  startExport()
}

…and then this in my Electron preload script that bridges node + browser js:

const { contextBridge } = require('electron')
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')

contextBridge.exposeInMainWorld('api', {
  startSaving: () => {
    if (fs.existsSync('frames')) {
      fs.rmSync('frames', { recursive: true })
    }
    fs.mkdirSync('frames')
  },
  saveFrame: (i, base64) => {
    fs.writeFileSync(
      path.join('frames', `${i.toString().padStart(4, '0')}.png`),
      base64.replace(/^data:image\/png;base64,/, ""),
      'base64',
    )
  },
  mix: async (fps) => {
    let name = ''
    let idx = 0
    do {
      name = 'export' + (idx ? ` (${idx})` : '')
      idx++
    } while (fs.existsSync(`~/Downloads/${name}.mp4`))

    execSync(`ffmpeg -r ${fps} -i frames/%04d.png -c:v libx264 -vf fps=${fps} -pix_fmt yuv420p -crf 18 ~/Downloads/${name}.mp4`)
    fs.rmSync('frames', { recursive: true })
  },
})

Additionally, the new p5.js mode for processing (GitHub - processing/processing-p5.js-mode: Write and run p5.js sketches on desktop using the p5.js mode · GitHub) uses Electron under the hood, allowing you to call out to node.js without the boilerplate of making a whole Electron project. So you could try a similar approach of saving files to the filesystem and running ffmpeg in there!

Serverside export

I think this probably doesn’t cover your use case yet, but my day job is working on a video editor where components in the project are p5 sketches: Butter for Developers It does serverside exporting where it steps through every frame capturing an image, so, a similar approach, but likely faster if you’re on an old computer. Where I think this currently falls short is that we max out at 2k resolution at the moment, and don’t have a 60fps toggle. Let me know if there are other things you’d want though!

1 Like