SnapDOM v3 is out
Automatic capture reuse, incremental recapture, fewer setup options and a live comparison you can run on your own machine.
SnapDOM 3.0.0 is out, along with v3 of the official plugins. Most of the work went into making repeated captures cheaper without returning stale content.
If you're new here: SnapDOM captures a DOM element with its styles and assets in the browser and exports SVG, PNG, JPG, WebP, canvas or blobs. The snapdom(element, options) entry point is unchanged in v3; the changes are underneath it.
The second capture matters
A share card gets exported several times unchanged. A dashboard updates one value between captures. Rebuilding everything each time repeats style reads, cloning and asset work that is still valid.
In v3, the first successful capture of an eligible static element creates a memo, and a later capture reuses it if the relevant state is unchanged. For supported local mutations, the engine rebuilds the affected subtrees and splices them into the previous capture, automatically.
Knowing when that is safe is the hard part. A local DOM edit can shift layout elsewhere. A scroll or focus change alters the image without changing the text. Canvas, video and iframe contents change with no DOM mutation at all.
So the incremental path is conservative: it runs only when the engine can preserve the full capture's output. Anything else falls back to a full capture, and frame-driven content captures fresh. Direct stylesheet edits such as sheet.insertRule() still need invalidate: true, since they produce no DOM mutations for style invalidation. Details in the cache guide.
Image exports from an existing result still use its captured state; if the page changes, call snapdom() again. PNG encoding and rasterization still cost time either way.
Concurrent captures now keep their working state in separate sessions instead of one shared mutable session.
Fonts by default, and a lot of small rendering fixes
Web fonts now embed automatically when the captured content uses them. The default is embedFonts: 'auto'; a capture using only system fonts skips that work. false leaves text fonts out, true forces discovery.
A large part of the release went into rendering details: fractional widths adding a line wrap, misaligned pseudo-element boxes, scrolled containers and images, transformed and offscreen elements. Small defects, but visible in every export.
V3 fixes those, preserves inherited fill colors in SVG <use> icons, and corrects how slotted Shadow DOM content is cloned. Font handling preserves icon styles and keeps unrelated font families when an icon font shares their stylesheet. The changelog links to the individual fixes and reports.
Two APIs for the work around a capture
fromString() is for input that is HTML rather than an element on the page. It mounts the markup offscreen in the live document, captures it and removes the mount. Page CSS and fonts apply.
import { snapdom } from '@zumer/snapdom'
const capture = await snapdom.fromString(
'<article class="share-card"><h2>SnapDOM v3</h2></article>'
)
const image = await capture.toPng({ width: 1200, dpr: 1 })
document.body.append(image)
The string goes into the real document through innerHTML, so use trusted markup or sanitize it first. A browser DOM is still required.
preCapture() prepares eligible captures on user intent. Enable it once and keep the capture call in your control's handler. For a button and card already mounted in your app:
const button = document.querySelector('#export-button')
const card = document.querySelector('#card')
snapdom.preCapture()
button.addEventListener('click', () =>
snapdom.download(card, { format: 'png', filename: 'card.png' })
)
The first capture started during the control's press or click event teaches SnapDOM which element and options belong to that control. On later hover or focus it prepares the same capture before the next click. A capture deferred to an unrelated timer will not teach it. No arguments, no background polling.
Core and official plugins share the v3 release line
The official @zumer/snapdom-plugins package is on v3 as well: HTML exports, text and JSON context, annotated element maps, image-based PDFs, image effects, and GIF or video recording. Match its major version to the core when you upgrade. Recording exports take new frames from the live element when recording starts.
Update, October 2026: the plugins moved to 4.x, which requires SnapDOM 3.3.0 or newer, and now include a paginated PDF exporter with selectable text and an editable Vector exporter.
Plugins also receive capture geometry, the exact export options and canvas cropping support, for exports that must line up with the source interface or share a crop with a core exporter. Examples in the plugin gallery; hooks and custom exports in the plugin reference.
The default renderer is still SVG. The native html-in-canvas engine needs a compatible browser and a custom build and is left out of the standard bundle.
Update, October 2026: Chrome 157 ships html-in-canvas without a flag, and the engine is now part of the default build. engine: 'html-in-canvas' paints the capture with Chrome's own renderer there and falls back to SVG everywhere else; the guide says when to use it.
Run the comparison on your own machine
The site got work too: the reference, capture recipes, framework guides, and the capture boundaries article on where SnapDOM runs when Node, browser automation or cross-origin frames are involved.
The part I most want people to try is the live comparison. Pick a scene and the libraries (html2canvas, html-to-image, modern-screenshot and others) and run it in your own browser on your own hardware.
Every library is timed through to a PNG data URL, with separate columns for first and repeat captures and the rendered output next to the timings. The regular scenes disable SnapDOM's capture memoization; the Polling scene exercises automatic reuse. Look at the images too: a fast result with missing content is a broken export. Pixel differences are measured against SnapDOM's output, a reference image rather than an independent correctness verdict.
A capability matrix captures small fixtures and checks whether their marker colors survive: Shadow DOM, pseudo-elements, painted canvas content and more. Everything runs in your browser, so scene, browser and hardware all matter.
Before upgrading from v2
This is a major release. Read the migration guide before bumping the version in an existing app. The changes that need a close look:
widthandheighttake precedence overscale. For a width of 400, usewidth: 400instead ofwidth: 200, scale: 2. Rasterdprstill applies; usedpr: 1for that exact pixel width.- Remove
preCacheand its subpath import. Capture reuse is automatic andpreCapture()learns intent; it is not a rename. - Remove the old
fast,burst,compress,resolvePicturePlaceholdersandpictureResolveroptions. Handle custom image loading and timeouts in your app before capture. - Core redaction masks password fields only. The
redactInputs()plugin masks other visible fields. afterExporthooks observe the export result; their return values no longer feed the next hook. UsedefineExportsfor custom output.
filter and exclude still work together, each with its own hide/remove mode. Function-valued filter, exclude, excludeStyleProps and fallbackURL make each new capture run fresh so callbacks read current application state. The v2 branch stays available for projects staying on that release.
npm install @zumer/snapdom@3 # If your app uses the official plugins: npm install @zumer/snapdom-plugins@3
Thanks to everyone who sent bug reports and small reproductions; several rendering fixes came from them. If a capture of your UI looks wrong on v3, open an issue with the browser, the source element and the exported result.