dom-to-image was the first library to popularise the SVG foreignObject trick for capturing the DOM, but its original package hasn't been actively maintained for years. SnapDOM picks up the same approach, modernises it, and adds web fonts, Shadow DOM, plugins and full pseudo-element support.
SnapDOM vs dom-to-image, head to head
Both libraries export the same DOM element to PNG five times. Times are averaged, the faster one is highlighted. Run it on your machine. The gap is hardware- and content-dependent.
A quick overview
dom-to-image introduced the technique most modern libraries still use: clone the node, inline its CSS and assets, wrap it in a <foreignObject>, and let the browser do the painting. It works, it's small, and the API is friendly. The downside is that it stopped receiving meaningful updates, so newer CSS features and edge cases are gradually catching up to it.
SnapDOM is a from-scratch implementation of the same idea, with active development and a plugin system on top. The capture pipeline is exposed through lifecycle hooks (beforeClone, afterClone, beforeRender, afterRender, beforeExport, afterExport) so you can transform the result without forking the library.
Feature matrix
| Feature | SnapDOM | dom-to-image |
|---|---|---|
| Active maintenance | Yes | Stalled |
Pseudo-elements (::before / ::after) | Full | Limited |
Web fonts (@font-face) | Embedded | Best-effort |
| Shadow DOM | Yes | No |
| CSS variables | Yes | Partial |
CSS counter() / counters() | Yes | No |
| Line-clamp / multi-line ellipsis | Yes | No |
| Plugin system | Yes | No |
| Output formats | SVG · PNG · JPG · WebP · Canvas · Blob | SVG · PNG · JPG · Blob |
| ES Module build | Yes | CommonJS |
| TypeScript types | Bundled | DefinitelyTyped |
| License | MIT | MIT |
When to use which
Choose SnapDOM when…
- You need active maintenance and bug fixes.
- Your UI depends on modern CSS features (variables, pseudo-elements, line-clamp).
- You use Web Components or Shadow DOM.
- You want to add custom export formats or transformations through plugins.
- You need first-class TypeScript types out of the box.
What staying on dom-to-image costs you…
- No maintenance: bugs with modern flexbox/grid layouts stay unfixed.
- Web fonts, Shadow DOM, CSS counters and line-clamp simply don't capture.
- CommonJS-era packaging and no bundled TypeScript types.
Same task, side by side
Capturing #card as a PNG and inserting the resulting image into the page:
dom-to-image
import domtoimage from 'dom-to-image'; const dataUrl = await domtoimage.toPng(document.querySelector('#card')); const img = new Image(); img.src = dataUrl; document.body.appendChild(img);
SnapDOM
import { snapdom } from '@zumer/snapdom'; const img = await snapdom.toPng(document.querySelector('#card')); document.body.appendChild(img);
SnapDOM returns the HTMLImageElement directly so you can drop the data-URL plumbing.
Migration snippet
Most dom-to-image options have a direct SnapDOM equivalent:
| dom-to-image | SnapDOM |
|---|---|
| domtoimage.toPng(el) | snapdom.toPng(el) |
| domtoimage.toJpeg(el, { quality: 0.9 }) | snapdom.toJpg(el, { quality: 0.9 }) |
| domtoimage.toBlob(el) | snapdom.toBlob(el) |
| domtoimage.toSvg(el) | (await snapdom(el)).toSvg() |
| { bgcolor: '#fff' } | { backgroundColor: '#fff' } |
| { width, height } | { width, height } |
| { filter: node => … } | { filter: node => … } |
| not supported | { embedFonts: true } |
| not supported | { plugins: [...] } |
// Before import domtoimage from 'dom-to-image'; const dataUrl = await domtoimage.toPng(el, { bgcolor: '#fff' }); // After import { snapdom } from '@zumer/snapdom'; const img = await snapdom.toPng(el, { backgroundColor: '#fff' });
Frequently asked questions
Is dom-to-image still maintained?
The original dom-to-image package on npm has not received a release in years. SnapDOM is a from-scratch, actively maintained implementation of the same foreignObject idea, with web fonts, Shadow DOM, plugins and full pseudo-element support on top, and a 1:1 migration path (see the table above).
How do I migrate from dom-to-image to SnapDOM?
Replace the import with @zumer/snapdom and call snapdom.toPng(element). Most options (filter, bgcolor, width, height) have a direct SnapDOM equivalent. See the migration table above.
Does SnapDOM support custom fonts?
Yes. Pass { embedFonts: true } and the matching @font-face declarations are inlined into the SVG output, so the captured image renders with your real fonts.
What about Shadow DOM?
Open Shadow DOM trees are walked and cloned by SnapDOM, so Web Components capture correctly. dom-to-image does not natively support this.
Test the migration
Install SnapDOM, run the equivalent capture and compare the output before replacing the existing call.
Open the demo Install from npm