English | 简体中文
SnapDOM is a high-performance DOM engine that captures a rendered element in the browser and exports it as PNG, SVG or canvas. Plugins turn the same capture into self-contained HTML, PDF, editable vectors, GIF, video or context for agents. Everything runs in the page, with no core dependencies.
Documentation and demos · Technical features · Official plugins · 简体中文
- Speed. Run the live benchmark in your browser: first and repeat captures against html2canvas and the other libraries, sorted by measured time. Unchanged elements reuse their last capture.
- Fidelity. The output matches what the browser painted. Every release runs a pixel suite of real demo pages before it ships.
- Flexibility. Capture once, export many formats. Official plugins add outputs, and the same plugin hooks let you write your own.
- Maintenance. About one release a week over the past year, MIT licensed, with a public changelog.
npm i @zumer/snapdomimport { snapdom } from '@zumer/snapdom';
const card = document.querySelector('#card');
const image = await snapdom.toPng(card);
document.body.appendChild(image);Capture once when you need several outputs:
const result = await snapdom(card);
const image = await result.toPng();
const canvas = await result.toCanvas();
const blob = await result.toBlob({ format: 'png' });
await result.download({ format: 'jpg', filename: 'card' });The result keeps that capture even if the source element later changes. Call snapdom(card) again to capture its new state.
| Use | Output | Provided by |
|---|---|---|
| Share a card, chart, invoice or dashboard | SVG, PNG, JPG, WebP, canvas or Blob | Core |
| Reuse a capture in a texture, overlay or transition | Canvas plus capture geometry | Core |
| Save a page fragment for later display | HTML with captured styles and fonts | html-export plugin |
| Give an agent or a log a view of page content | Text/JSON context, or an image with an element map | context-export / agent-map plugins |
| Download a document or record changing content | Searchable, paginated PDF, animated GIF or browser-encoded video | pdf / gif-export / video-export plugins |
| Export editable artwork or paste into Figma | Native SVG shapes and text, or a Figma clipboard payload | vector plugin |
Image, HTML and context exports use the captured state. GIF and video plugins record the live element over time.
Install the core and, when you need them, the official plugins:
npm i @zumer/snapdom@latest @zumer/snapdom-plugins@latestPlugins 4.x require SnapDOM 3.3.0 or newer within 3.x. They include the PDF and Vector exporters, MIT licensed like the core. Their sources live in packages/plugins/.
Or load it in a browser:
<script src="https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.js"></script>
<script>
snapdom.toPng(document.querySelector('#card')).then(image => {
document.body.appendChild(image);
});
</script>As an ES module from a CDN:
import { snapdom } from 'https://esm.sh/@zumer/snapdom@latest';
import { htmlExport } from 'https://esm.sh/@zumer/snapdom-plugins@latest/html-export';https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.mjs serves the same module. These examples load the latest published core and plugins.
| File | Use |
|---|---|
dist/snapdom.mjs |
ES module for imports and bundlers |
dist/snapdom.js |
Script tag exposing window.snapdom |
types/snapdom.d.ts |
TypeScript declarations |
There is no CommonJS build. @zumer/snapdom/plugins and the package root share the same runtime and plugin registry.
| Result method | Returns |
|---|---|
toPng(), toJpg(), toWebp() |
An HTMLImageElement |
toSvg() |
An SVG-backed HTMLImageElement |
toCanvas() |
An HTMLCanvasElement |
toBlob() |
An SVG Blob unless a format was explicitly set on the capture or export |
toRaw() / url |
The capture's SVG data URL |
download() |
Downloads the chosen format |
to(name, options?) |
Runs a core or plugin exporter by name |
One-step shortcuts such as snapdom.toPng(element, options) capture and export in one call. Results also have toJpeg() as an alias for toJpg(). toImg() remains available; prefer toSvg() for an SVG image.
const result = await snapdom(card, {
width: 800,
dpr: 1,
backgroundColor: '#ffffff',
exclude: '.capture-ignore',
excludeMode: 'remove'
});width and height define output size. If only one is set, the aspect ratio is preserved. scale applies when neither is set, and dpr multiplies the pixel dimensions. download() always uses DPR 1, so the file matches the element's CSS size; pass scale: devicePixelRatio for a file as sharp as the screen.
| Common option | Default | Purpose |
|---|---|---|
scale / dpr |
1 / device pixel ratio |
Output resolution |
width / height |
Unset | Output dimensions |
embedFonts |
'auto' |
Embed the web fonts the capture uses |
backgroundColor |
Transparent; white for JPG/WebP | Output background |
exclude |
None | Selectors or predicates; true means exclude |
excludeMode |
'hide' |
Keep an invisible spacer, or use 'remove' |
filter |
None | Predicate; true keeps a node and false filters it out |
filterMode |
'hide' |
Independent layout mode for nodes rejected by filter |
clip |
Unset | Capture the viewport or a page-coordinate rectangle |
captureSelection |
false |
Include the user's text selection |
canvas |
Unset | Reuse an existing canvas |
invalidate |
false |
Refresh after changes such as programmatic CSSOM edits |
fast |
true |
false keeps the page responsive during long captures |
debug |
false |
Log fallbacks and errors to the console; without it SnapDOM stays quiet and result.warnings reports them |
All options include shadows, transforms, fonts, CORS, fallbacks and layout reconciliation.
With the default SVG engine, PNG and JPEG file exports automatically handle captures beyond the browser's canvas limits. Capture once, then download one full-resolution file or get its Blob:
const capture = await snapdom(article);
await capture.download({ format: 'png', scale: 2 });
// Or upload/store the file yourself:
const blob = await capture.toBlob({ format: 'png', scale: 2, dpr: 1 });
// JPEG works the same way, up to 65,535px per side:
await capture.download({ format: 'jpg', scale: 2, quality: 0.9 });SnapDOM renders the frozen capture in windows and encodes them into one PNG or JPEG. It preserves the requested dimensions, including shadows and padding, without allocating a canvas for the whole image. download() always uses DPR 1; toBlob() uses the capture's DPR unless you override it. Ordinary-sized exports use the native encoder.
PNG requires CompressionStream; older browsers keep the existing downscale and warning, as does a JPEG past 65,535px per side, the format's limit. WebP cannot go past 16,383px per side. Very large files still take time and memory to encode, and the application opening them has its own image limits.
toCanvas(), toPng(), toJpg() and WebP exports still use a single canvas and scale down past SnapDOM's size guards (16,384px per side in Safari, 32,767px in Chrome/Firefox, plus an area limit). When you need separate canvases, use crop:
const capture = await snapdom(article);
const { contentX, contentY, w0, h0 } = capture.meta;
const pieceHeight = 4000; // CSS px
for (let y = 0; y < h0; y += pieceHeight) {
const canvas = await capture.toCanvas({
scale: 2,
dpr: 1,
crop: { x: contentX, y: contentY + y, width: w0, height: Math.min(pieceHeight, h0 - y) }
});
// save or upload this piece, then move on
}crop is a rectangle, not a switch. It is in the coordinates of capture.meta, and each call returns one canvas. The capture runs once; only the rasterization repeats. Keep pieceHeight × scale × dpr under the limit, and set dpr yourself, since it defaults to the device pixel ratio. How tiling works.
import { htmlExport, contextExport } from '@zumer/snapdom-plugins';
const result = await snapdom(card, {
plugins: [htmlExport(), contextExport({ format: 'json' })]
});
const html = await result.toHtml();
const context = await result.toContext();The same plugin system supports overlays, redaction and custom exporters. Local plugins override global plugins by name. See the official plugin reference and plugin specification.
PDF returns a Blob with searchable, selectable text, links, pagination and optional form fields. Vector returns an SVG string without <foreignObject>, preserving supported shapes and editable text.
import { snapdom } from '@zumer/snapdom';
import { pdf } from '@zumer/snapdom-plugins/pdf';
import { vector } from '@zumer/snapdom-plugins/vector';
const documentCapture = await snapdom(element, { plugins: [pdf()] });
const blob = await documentCapture.toPdf({ page: 'a4', margin: 36 });
const artwork = await snapdom(element, { plugins: [vector()] });
const svg = await artwork.toVector();
copyButton.addEventListener('click', () => artwork.toFigma());Pass PDF capture settings to pdf() and paper, margin and download settings to toPdf(). toFigma() returns Promise<void> and needs the async clipboard API on HTTPS or localhost, called from a user action. Vector requires a connected source element; unsupported paint is diagnosed by the engine. See the PDF reference and Vector contract.
const result = await snapdom.fromString('<article>Hello</article>');
const image = await result.toPng();Sanitize untrusted HTML first, with DOMPurify or equivalent. The string is parsed and activated like markup you wrote yourself: inline handlers such as <img onerror> run in the caller's origin, and can keep running after the mount is removed. fromString() mounts the markup offscreen and removes it after capture.
const canvas = document.createElement('canvas');
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
async function refresh(element) {
await snapdom.toCanvas(element, { canvas, scale: 1, dpr: 1 });
texture.needsUpdate = true;
}result.meta contains the capture geometry needed to place an exported image over the source interface. The labs show textures, mirrors and transitions.
- SnapDOM needs a browser DOM. A server-side Node.js process needs a browser environment to run it.
- Cross-origin images, fonts and stylesheets need readable resources or an appropriate proxy.
crossorigindoes not grant access unless the server also allows it. Cross-origin iframes use placeholders. - SVG output includes HTML inside
<foreignObject>. It is suitable for browsers; support varies in other SVG viewers and document tools. - Output depends on browser rendering and image limits; for tall PNG files see Export a long page. Safari may fall back to PNG when WebP encoding is unavailable.
- Canvas, video and other changing surfaces are captured fresh. JavaScript CSSOM edits are not observable automatically; use
invalidate: trueafter them. - Core captures visible input values. Semantic plugins redact sensitive field values in their text/map output, but their attached image needs
redactInputsorexcludeif you want those pixels hidden too.
Technical features and browser behavior covers the details.
- Eligible unchanged captures reuse the first result. Safe local changes rebuild only affected subtrees; other changes use a full capture.
- Web fonts embed automatically when used. System-font captures skip that work.
- The style pass avoids redundant reads, and per-capture state is isolated for concurrent captures.
- Safari image decoding and drawing retain their browser-specific handling.
snapdom.preCapture()can prepare captures on user intent. It learns a control when a capture starts during its press/click event, then prepares that capture on later hover or focus.
snapdom.preCapture();
button.onclick = () => snapdom.toPng(card);Image compression and resource caching remain automatic. They do not make rasterization or image encoding free. See performance measurements and the cache guide.
SnapDOM has two rendering engines: SVG, the default, and html-in-canvas, which paints the same captured clone through the browser's native canvas API. Select the second with engine: 'html-in-canvas'.
The second engine ships in the default build and runs where the browser has the API: Chrome 157 and newer, without a flag. Firefox and Safari have no implementation, so there, and on any capture it cannot paint, SnapDOM runs the SVG engine on the same clone and you get the same result object. Captures of body or html, transformed or zoomed roots, clip, width/height and outerShadows use SVG, and so do captures with a plugin that has a beforeRender or afterRender hook (html-export, pdf, vector, and redact-inputs with blocks or attributes); every other official plugin runs on the native engine. A successful native capture produces a bitmap, so its URL and toRaw() return PNG, not serialized SVG. SVG stays the default because it is faster (the native paint waits for a rendering update, about 35 ms on a small card where SVG takes 3, see BENCHMARKS.md); pick html-in-canvas when scrollbars, or anything else the SVG image draws differently, have to match the page. Both engines paint the same clone, so <audio controls>, <progress> and <meter> look the same with either one, and not quite like the page. See ARCHITECTURE.md and the guide.




