SnapDOMGitHub★ 8K
Documentation
zumerlab/snapdom …

SnapDOM Options

Set output size, load capture assets, exclude private content and control clipping. These options describe SnapDOM v3.x.x.

All options API reference

All options

All capture methods accept an options object.

OptionTypeDefaultDescription
debugbooleanfalseLog suppressed errors to console.warn.
scalenumber1Output scale, used only when neither width nor height is set.
dprnumberdevicePixelRatioDevice pixel ratio for raster output.
width / heightnumbernullAbsolute output size; one dimension preserves aspect ratio.
formatpng | jpeg | jpg | webp | svgpngDefault for format-selecting exports; named helpers choose their codec. jpg resolves to jpeg.
typepng | jpeg | jpg | webp | svgundefinedDeprecated alias kept synchronized with canonical format; prefer format, though either name is honored.
backgroundColorstring | nullnullBackground fill; defaults to #ffffff only for JPEG/WebP.
qualitynumber0.92JPEG/WebP quality from 0 to 1.
filenamestringsnapDOMDownload filename base.
embedFontsboolean | "auto""auto"Embed used webfonts; skip the font phase on system-font-only pages. true forces discovery; false skips text-font embedding.
localFontsarray[]Local fonts { family, src, weight?, style?, stretchPct? }.
iconFontsstring | RegExp | Array[]Extra icon-font matchers.
excludeFontsobjectundefinedExclude font families, domains or subsets.
fontStylesheetDomainsstring[][]Extra domains allowed for cross-origin font CSS.
excludestring | function | Array[]Selectors and/or predicates; returning true excludes the node.
excludeMode"hide" | "remove""hide"Keep an invisible spacer or remove the node.
filterfunctionnullInclusion predicate; true keeps a node, false filters it out. Can be used together with exclude.
filterMode"hide" | "remove""hide"Independent layout mode for nodes rejected by filter.
clip"viewport" | rectnullCapture only a viewport or page-coordinate rectangle.
useProxystring''CORS proxy template/base.
fallbackURLstring | functionundefinedFallback for a broken <img>. A function reads current state when a new capture needs a fallback.
placeholdersbooleantrueShow placeholders for failed resources/CORS iframes.
outerTransformsbooleantrueKeep the root's rotation and scale/skew. false removes rotation while preserving scale/skew; root translation is normalized.
outerShadowsboolean | "subtree"falseStrip or bound root effects; "subtree" also bounds descendant shadow ink. Blur bleed is included unless clip fixes the edges.
reconcilebooleanfalseMeasure and pin clone boxes that diverge from the live DOM.
fastboolean | 'auto'trueWhether a long capture may pause so the page keeps painting and taking input. 'auto' is experimental.
invalidatebooleanfalseForce one fresh capture that is not served from the existing memo and clear style snapshots after unobservable application changes. A stable fresh result may become the new memo.
captureSelectionbooleanfalseRender the live text or field selection.
canvasHTMLCanvasElementnullReuse an existing raster target.
excludeStylePropsRegExp | functionnullSkip matching computed-style properties. A function is evaluated afresh for each new capture.
cache"soft" | "disabled" | "auto" | "full" | false"soft"Controls persistent resource/style caches only. disabled/false clears and bypasses them; legacy values map to soft. Repeat memoization is separate.
pluginsarrayundefinedPer-capture plugin definitions.
engine"svg" | "html-in-canvas""svg"Select one of SnapDOM's two integrated render engines. svg serializes the finished clone; experimental html-in-canvas paints that same clone through the browser's native API, with SVG fallback.

html-in-canvas still requires the browser's experimental feature flag or applicable origin trial; supported Chrome builds expose it through chrome://flags/#canvas-draw-element. It also needs a build compiled with SNAPDOM_CANVAS_ENGINE=1 npm run compile; the default build currently omits it. Unsupported captures fall back to SVG, including geometry and render hooks the native engine cannot handle.

A successful native capture is a bitmap: url/toRaw() lazily encode PNG, toSvg() returns a PNG-backed image, and toBlob() defaults to PNG unless a format was explicitly selected. Requesting an SVG Blob from that bitmap rejects. Select engine: 'svg' when serialized SVG is required.

debug

Set debug: true to log suppressed errors with the [snapdom] prefix. The result also exposes warnings for recorded capture fallbacks and limits.

await snapdom.toPng(el, { debug: true });

Automatic image downsampling

SnapDOM reduces oversized raster assets to the resolution the requested output needs, accounting for output size, transforms and DPR. It preserves aspect ratio and never upscales the source. This covers <img>, captured canvas/video frames, non-repeating backgrounds and SVG <image>.

The benefit depends on the export:

The SVG engine keeps the original asset data for later exports at a larger resolution. Images that already fit the requested resolution are left unchanged. A successful native-engine capture is already a bitmap, so enlarging it scales those captured pixels.

Fallback image on <img> load failure

Provide a default image for failed <img> loads. You can pass a fixed URL or a callback that receives measured dimensions and returns a URL (handy to generate dynamic placeholders).

// 1) Fixed URL fallback
await snapdom.toSvg(element, {
  fallbackURL: '/images/fallback.png'
});

// 2) Dynamic placeholder via callback
await snapdom.toSvg(element, {
  fallbackURL: ({ width = 300, height = 150 }) =>
    `https://placehold.co/${width}x${height}`
});

// 3) With proxy (if your fallback host has no CORS)
await snapdom.toSvg(element, {
  fallbackURL: ({ width = 300, height = 150 }) =>
    `https://dummyimage.com/${width}x${height}/cccccc/666.png&text=img`,
  useProxy: 'https://proxy.corsfix.com/?'
});

Notes:

Dimensions (scale, width, height)

Cross-Origin Images & Fonts (useProxy)

SnapDOM fetches assets directly when their origin and CORS policy allow it. Set useProxy to a proxy you trust for assets the browser cannot read directly:

await snapdom.toPng(el, {
  useProxy: 'https://your-proxy.example/?url='
});

Fonts

embedFonts

The default, 'auto', embeds webfonts used by the capture and skips font discovery for system-font-only content. Use true to force discovery or false to skip text-font embedding. Icon-font glyphs use a separate rendering path.

localFonts

Declare font files or data URLs explicitly when stylesheet discovery cannot supply them:

await snapdom.toPng(el, {
  embedFonts: true,
  localFonts: [
    { family: 'Inter', src: '/fonts/Inter-Variable.woff2', weight: 400, style: 'normal' },
    { family: 'Inter', src: '/fonts/Inter-Italic.woff2', style: 'italic' }
  ]
});

iconFonts

Add custom icon families (names or regex matchers). Useful for private icon sets:

await snapdom.toPng(el, {
  iconFonts: ['MyIcons', /^(Remix|Feather) Icons?$/i]
});

excludeFonts

Skip specific non-icon fonts to speed up capture or avoid unnecessary downloads.

await snapdom.toPng(el, {
  embedFonts: true,
  excludeFonts: {
    families: ['Noto Serif', 'SomeHeavyFont'],     // skip by family name
    subsets: ['cyrillic-ext']                      // skip by unicode-range subset tag
  }
});

Notes

Selecting content: filter and exclude

filter and exclude remain independent, as in v2.x.x. Use them together in one capture, with separate layout modes. V3 adds optional predicates to exclude; this does not replace filter. The separate CSS-effect plugin named filter is also available.

// Supported in v2 and v3: hide private fields, remove the toolbar.
await snapdom(el, {
  filter: node => !node.matches('[data-private]'),
  filterMode: 'hide',
  exclude: ['.toolbar'],
  excludeMode: 'remove'
});

Per node, data-capture="exclude" is checked first, then exclude, then filter. The first omission determines its mode and stops evaluation for that node. The attribute uses excludeMode; a matching exclude also wins if filter would reject the same node with a different mode. A true filter result does not override an exclusion.

Example: leave out elements with display:none:

/**
 * @param {Element} el
 * @returns {boolean} true = EXCLUDE this node (`filter` uses the opposite polarity)
 */
function isHidden(el) {
  return window.getComputedStyle(el).display === 'none';
}

await snapdom.toPng(document.body, { exclude: isHidden });

Example: mixing selectors and a predicate:

await snapdom.toPng(el, {
  exclude: ['.cookie-banner', (node) => node.dataset.secret === 'true'],
  excludeMode: 'remove'
});

Example with exclude: remove banners or tooltips by selector

await snapdom.toPng(el, {
  exclude: ['.cookie-banner', '.tooltip', '[data-test="debug"]']
});

Function-valued rules are evaluated when applicable on every new capture, including when the same function reads changing application state. They suspend unchanged-capture memoization; no invalidate is needed just because their closure changed. An already returned result keeps its original content until you capture again.

Other v2 option changes

Compared with v2.x.x, the following settings no longer belong to the public v3 options. The underlying capture capabilities remain:

See the migration guide for removed APIs, TypeScript names and plugin-hook changes.

outerTransforms

outerTransforms: false removes the captured root's rotation while retaining its scale and skew. Root translation is normalized during capture. Descendant transforms keep their own layout.

outerShadows

Note: outerShadows: false does not promise a bleed-free box: authored blur() remains visible and its extent is still included. An explicit clip is the exception: its edges are exact and never expand for bleed.

Example

// Keep root transforms and include root shadow bleed
await snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });

reconcile

With the SVG engine, captured HTML lays out inside <foreignObject>. Inline text and table cells can wrap differently when font metrics or layout constraints differ from the live page.

reconcile: true measures the styled clone offscreen against the live subtree and pins boxes that diverge. It adds a layout pass and can roughly double capture time. Use it when you see wrapping differences; SnapDOM also emits a one-time suggestion for captures that may benefit.

await snapdom.toPng(el, { reconcile: true });

fast

A capture reads the computed style of every node in one pass. On a large tree, such as a data table built from web components, that pass can hold the main thread for hundreds of milliseconds: the page stops painting and responding until it ends.

fast: false lets the capture give the page a turn about every frame, so scrolling, typing and animations continue while it runs. true, the default, captures in one task. The pauses add little to the total time.

fast: 'auto' is experimental and may change: it pauses only once a capture runs past 40 ms, so short captures never pause.

If the captured element changes while a capture is paused, SnapDOM clones it again in one task, so the image shows a state the page really had. That check uses the watchers behind repeat-capture memoization, so a capture that is never memoized (frame-driven trees, function-valued callbacks, captureSelection) keeps what it read.

await snapdom.toPng(table, { fast: false });

Automatic repeat-capture memoization

Capturing the same unchanged element repeatedly would otherwise re-walk the tree and re-read every node's computed style on every call. SnapDOM memoizes eligible static elements from their first capture. Observable DOM, style, media and interaction changes invalidate that result; frame-driven content that cannot be observed reliably is captured fresh. A mutation may trigger a conservative full capture or a differential rebuild when that rebuild is provably equivalent. At most 64 elements stay memoized; the least recently captured one is released.

// Stable captures can reuse the result; the text mutation invalidates it.
for (let i = 0; i < 10; i++) {
  if (i === 3) counter.textContent = 'new value';
  const result = await snapdom(el);
  img.src = (await result.toPng()).src;
}

Frame-driven canvas/video/iframe trees already bypass the memo and capture fresh. Programmatic CSSOM edits (stylesheet.insertRule/deleteRule, cssRule.style.* on a rule rather than an element) expose no browser signal, so pass invalidate: true on the next call. That call is not served from the existing memo and clears style snapshots; if its fresh result is stable, it may become the new memo:

sheet.insertRule('.card { color: rebeccapurple }');
await snapdom(el, { invalidate: true });

Function-valued filter, exclude, excludeStyleProps or fallbackURL already force a new capture and reevaluate applicable callback decisions. A changed closure needs no invalidate. Previous style/fallback decisions are not reused for those callbacks. Exporting an existing result still uses its original captured state.

Ready to capture?

Try the browser demo, then use the API reference to choose your output.

Open the demo Install from npm