Render function called twice while exporting
See original GitHub issueI noticed while experimenting with canvas-sketch that when you export an image using <kbd>cmd</kbd> + <kbd>s</kbd> or an animation using <kbd>cmd</kbd> + <kbd>shift</kbd> + <kbd>s</kbd>, the render function is called twice for each frame.
To reproduce:
// bug-demo.js
const SKETCH = require('canvas-sketch')
const sketch = () => {
return ({ frame }) => {
console.log(`render frame ${frame}`)
}
}
SKETCH(sketch)
Then launch:
canvas-sketch bug-demo.js --open
Open the browser console and then try saving with <kbd>cmd</kbd> + <kbd>s</kbd> and you should see “render frame 0” logged twice.
Here are the relevant lines from my console (Firefox 62.0.3):

“render frame 0” is logged once on page load, then twice more on export/save. When exporting an animation the duplication happens for each frame.
I imagine this has performance implications, but I also tripped up on it because I was using a generator function to get the next value to display for each frame. Each frame was being rendered twice (and saved once), which meant every other value returned from the generator was lost.
P.S. Generally the experience getting started with this has been really great. Thanks for the thoughtful library and clear documentation!
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (4 by maintainers)

Top Related StackOverflow Question
Thanks for the issue report! This is actually intentional, and not a bug. I’ll explain why:
In many sketches the visible size on screen (within your browser) will not match the export size. For example, if you are using
{ scaleToView: true }it allows you to export huge print sizes without rendering such massive canvases to the browser each frame. In these cases, upon Cmd + S, we have to render the canvas to the full export size, then capture its data URL, and then resize and re-render it back down to its original browser size to avoid an ugly “flash” of large canvas in the browser, and to ensure the user continues to see the scaled canvas in the browser.Another situation is with
{ exporting }prop — sometimes you have a sketch where a certain feature should only be used during export (like ignoring debug rendering that you just want during a browser experience). In this case, we render twice: the first render says “this frame is being exported” and the second says “this frame is not being exported, but instead just visualized in the browser.” This way there is no flash of content while looking at the browser.Just like with other declarative frameworks (React, Vue, etc) you should try not to assume too much about when the
render()function will be called, as it may also get called in other situations such as on resize. Instead of creating side-effects in your render function, you should try to make it a pure function, such as by operating on the{ frame }prop instead of increasing state on each render.I’d also be happy to explore other solutions, if you let me know a bit more about why you needed to use a generator function. Maybe there is some syntax or documentation I could improve in canvas-sketch.
Cheers!
beginandendcan be used alongsidetickandrender. These things are still experimental but here is my plan so far:beginis called at the start of playback, and also the beginning of each subsequent loop (so you can, for example, randomize parameters on each loop of an animation)endis called after rendering the final frame, i.e. at the end of each loop (or never called if there is no finite duration to the animation)tickis called before each frame, and before rendering each frame. Allows you to step physics and logic and move your animation forward.renderis called aftertickin the animation loop, but also after the canvas is resized (i.e. by browser resize) and again just before the export (as the{ exporting }prop has changed).The second canvas is a cool idea but doing it automatically under the hood is just too magical for my taste, and could introduce a lot of new problems. I think its best that
{ canvas, context }props point to the same element that is visible in the DOM.