Export is a product feature
A screenshot button sounds small, but users rely on it for design reviews, bug reports, thumbnails, and documentation. If the export is blank or stale, the whole tool feels unreliable. Treat screenshot export as part of the product, not a console trick.
The export should have a clear filename, a known background choice, and a predictable resolution. If the user changes the scene, render a fresh frame immediately before creating the blob.
Prefer blobs for downloads
Data URLs are convenient for quick tests but can become large strings. `canvas.toBlob` gives you a Blob that can be downloaded through an object URL. It is a better fit for a real button and keeps memory usage easier to reason about.
If the tool supports both transparent and solid backgrounds, apply that setting before render. A user should not have to guess why their exported PNG looks different from the preview.
Canvas does not include the DOM
Labels, buttons, and HTML overlays outside the canvas will not appear in canvas export. If labels matter, render them inside WebGL, use a separate DOM capture pipeline, or tell users that the export is scene-only. Clear limitation text is better than a surprising file.
For a Three.js lab, scene-only export is often exactly what users need: a clean asset preview they can drop into a document or design board.
Make export state visible
A tiny status line that says 'PNG saved' or 'Export failed' makes the feature feel finished. Avoid browser alerts. Keep the status near the export button and use plain language. If export fails because the canvas is tainted by cross-origin assets, explain that the source image server must allow canvas use.
The boring edge cases are the ones people remember. Handle them with dignity and the tool feels much more trustworthy.
Export from a scene with known canvas content
The examples gallery supplies small scenes for checking render timing and the difference between WebGL pixels and surrounding DOM.
- Canvas contains a rendered frame
- DOM labels are not part of canvas output
- Filename and MIME type are explicit
Render immediately before capture
Capturing an old or cleared drawing buffer can produce an empty image.
downloadLink.href = renderer.domElement.toDataURL();
downloadLink.click();renderer.render(scene, camera);
renderer.domElement.toBlob((blob) => {
if (!blob) return showExportError();
downloadBlob(blob, 'threejs-scene.png');
}, 'image/png');Export contract
The output should describe exactly what the browser is asked to create.
| Property | Recorded choice |
|---|---|
| Format | image/png |
| Capture source | WebGL canvas only |
| Frame timing | Explicit render before toBlob |
| Transparent background | Only when renderer alpha and clear alpha allow it |
Verify a screenshot button
Test visible output, failure feedback, and repeated use.
- Render the intended camera frame immediately before capture.
- Create a Blob and reject a null result with an inline message.
- Download with a deterministic filename and revoke the object URL.
- Repeat after resize and after changing the scene background.
Export button handler
async function downloadCanvasPng(canvas, renderer, scene, camera) {
renderer.render(scene, camera);
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/png'));
if (!blob) throw new Error('Canvas export failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `threejs-export-${Date.now()}.png`;
link.click();
URL.revokeObjectURL(url);
}
Sources and further reading
Update record
- : Rewritten around a reproducible demo, explicit test record, code comparison, and known platform limits.