SnapDOMGitHub8K
Documentation
zumerlab/snapdom

SnapDOM Options

Every capture option SnapDOM accepts, explained one by one: scale and DPR, font embedding, cross-origin proxying, node filtering, image compression, and the transform/shadow bleed controls.

All options API reference

All options

All capture methods accept an options object.

OptionTypeDefaultDescription
debugbooleanfalseWhen true, logs suppressed errors to console.warn for troubleshooting
fastbooleantrueSkips small idle delays for faster results
embedFontsbooleanfalseInlines non-icon fonts (icon fonts always on)
localFontsarray[]Local fonts { family, src, weight?, style? }
iconFontsstring | RegExp | Array[]Extra icon font matchers
excludeFontsobject{}Exclude families/domains/subsets during embedding
scalenumber1Output scale multiplier
dprnumberdevicePixelRatioDevice pixel ratio
widthnumber-Output width
heightnumber-Output height
backgroundColorstring"#fff"Fallback color for JPG/WebP
qualitynumber1Quality for JPG/WebP (0 to 1)
useProxystring''Proxy base for CORS fallbacks
excludestring[]-CSS selectors to exclude
excludeMode"hide" | "remove""hide"How exclude is applied
filterfunction-Custom predicate (el) => boolean
filterMode"hide" | "remove""hide"How filter is applied
cachestring"soft"disabled | soft | auto | full
placeholdersbooleantrueShow placeholders for images/CORS iframes
fallbackURLstring | function-Fallback image for <img> load failure
outerTransformsbooleantrueWhen false removes translate/rotate but preserves scale/skew, producing a flat, reusable capture
outerShadowsbooleanfalseDo not expand the root's bounding box for shadows/blur/outline, and strip those visual effects from the cloned root
compressbooleantrueDownsample inlined raster images to their visible resolution. Faster raster captures + smaller SVG output, fidelity-neutral. Set false to embed images verbatim
reconcilebooleanfalseMeasures the styled clone against the live DOM and pins any box whose size diverges. Fixes rare text re-wrap/layout drift at the cost of roughly doubling capture time
burstbooleanfalseMemoizes repeated captures of this element via a scoped MutationObserver, so an unchanged repeat skips the pipeline entirely
invalidatebooleanfalseWith burst: true, forces a fresh capture for changes automatic tracking can't see (canvas draws, programmatic CSSOM edits)

debug

When debug: true, SnapDOM logs normally suppressed errors to console.warn (with the [snapdom] prefix). Useful for troubleshooting capture issues (canvas failures, blob resolution, style stripping, etc.) without noisy output in production.

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

compress

Inlined raster images are embedded at their full natural resolution even when shown in a small box: pixels the output can never display, which only bloat the payload and slow rasterization. With compress (on by default) SnapDOM downsamples each oversized raster to (a touch below) the resolution actually visible (display box × scale × dpr), preserving aspect ratio and never upscaling. The source codec is kept (PNGs stay lossless), and because the image was already shown far smaller than its natural size the quality loss is imperceptible in practice. Images already at or below their visible size are left untouched. Covers <img> (incl. cloned canvas/video), non-repeating CSS background-image, and SVG <image>.

The benefit depends on the export:

It is a no-op for images already shown at (or below) their natural resolution, so the common case pays virtually nothing. Pass compress: false to embed images verbatim (e.g. if you intend to scale the SVG up later).

// default: images downsampled to visible resolution
await snapdom.toPng(el);

// keep images at full resolution
await snapdom.toPng(el, { compress: false });

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)

By default SnapDOM tries crossOrigin="anonymous" (or use-credentials for same-origin). If an asset is CORS-blocked, you can set useProxy to a prefix URL that forwards the actual src:

await snapdom.toPng(el, {
  useProxy: 'https://proxy.corsfix.com/?' // Note: Any cors proxy could be used 'https://proxy.corsfix.com/?'
});

Fonts

embedFonts

When true, SnapDOM embeds non-icon @font-face rules detected as used within the captured subtree. Icon fonts (Font Awesome, Material Icons, etc.) are embedded always.

localFonts

If you serve fonts yourself or have data URLs, you can declare them here to avoid extra CSS discovery:

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

Filtering nodes: exclude vs filter

Example: filter out elements with display:none:

/**
 * Example filter: skip elements with display:none
 * @param {Element} el
 * @returns {boolean} true = keep, false = exclude
 */
function filterHidden(el) {
  const cs = window.getComputedStyle(el);
  if (cs.display === 'none') return false;
  return true;
}

await snapdom.toPng(document.body, { filter: filterHidden });

Example with exclude: remove banners or tooltips by selector

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

outerTransforms

When capturing rotated or translated elements, you may want use outerTransforms: false option if you want to eliminate those external transforms. So, the output is flat, upright, and ready to use elsewhere.

outerShadows

Tip: Using both (outerTransforms: false + outerShadows: false) produces a strict, minimal bounding box with no visual bleed.

Example

// outerTransforms and remove shadow bleed
await snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });

reconcile

SnapDOM translates live computed styles into a generated CSS class per element, which lays out inside a <foreignObject> instead of the page's own stylesheet cascade. That translation matches the live DOM almost everywhere, but a narrow class of boxes (inline text, table cells) get their width from layout algorithms rather than an authored value, and can re-wrap a hair differently if the SVG rasterizer falls back to a slightly different font metric.

With reconcile: true, SnapDOM mounts the styled clone off-screen in the real document, measures every node against its live counterpart, and pins width/height only on the boxes that actually diverge: measurement instead of a heuristic, so it's always correct where it applies. The cost is a second real layout pass over the whole captured subtree, which roughly doubles capture time, so it's opt-in rather than default. If SnapDOM detects a capture that used width-softening on the kind of element known to sometimes re-wrap, it logs a one-time console.warn suggesting this option. That warning only fires when reconcile isn't already set.

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

burst

Capturing the same unchanged element repeatedly (dashboard polling, a GIF/video export loop) re-walks the tree and re-reads every node's computed style on every single call: real work, even with every image/font/background already cached.

burst: true watches the element with a scoped MutationObserver and memoizes the last result: an unchanged repeat capture returns the cached result instantly, skipping the pipeline entirely. Any external mutation of the subtree (or of document styles in <head>) marks it dirty again, so the next capture re-runs for real. <video> frame changes (seek, playback) are tracked too, since they touch no DOM attribute. It's opt-in: a persistent observer per element is wasted cost on a one-shot capture.

// Dashboard polling: only the first and the 3rd (mutated) calls do real work.
for (let i = 0; i < 10; i++) {
  if (i === 3) counter.textContent = 'new value';
  const result = await snapdom(el, { burst: true });
  img.src = (await result.toPng()).src;
}

Canvas pixel draws and programmatic CSSOM edits (stylesheet.insertRule/deleteRule, cssRule.style.* on a rule rather than an element) touch no DOM attribute either, so the automatic tracking can't see them. Pass invalidate: true alongside burst: true on the next call to force a fresh capture after one of those:

ctx.fillRect(0, 0, 100, 100); // canvas redraw — invisible to the observer
await snapdom(el, { burst: true, invalidate: true });

Discoverability: if you capture the same element 3+ times within 2s without burst: true, SnapDOM logs a one-time console.warn suggesting it. That's the only case the (cheap) tracking runs unconditionally.

Test an option

Drop SnapDOM into your project and capture your first element in one line.

Open the demo Install from npm