html2canvas hasn't shipped a stable release since 2022 and explicitly recommends against production use. SnapDOM is actively maintained, captures pseudo-elements, web fonts, Shadow DOM and CSS variables with full fidelity, supports a plugin pipeline, and is faster on the same DOM. Both are MIT-licensed and run client-side.
SnapDOM vs html2canvas, head to head
Both libraries capture the same DOM element 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
html2canvas works by reading the DOM and re-painting every element onto a <canvas> using its own CSS engine. It has been around since 2012, has wide adoption, and a long backlog of edge cases that come from re-implementing the browser's renderer.
SnapDOM takes a different approach: it deep-clones the DOM, inlines the styles, fonts and images, and wraps the clone in an SVG <foreignObject>. The browser does the actual rendering, which means anything the browser can paint, SnapDOM can capture — including pseudo-elements, CSS variables, custom counters and Shadow DOM.
Feature matrix
| Feature | SnapDOM | html2canvas |
|---|---|---|
Pseudo-elements (::before / ::after) | Full | Partial |
Web fonts (@font-face) | Embedded | Best-effort |
| Shadow DOM | Yes | No |
| CSS variables | Yes | Partial |
CSS counter() / counters() | Yes | No |
Same-origin <iframe> capture | Yes | Limited |
| Line-clamp / multi-line ellipsis | Yes | No |
| Plugin system | Yes | No |
| Output formats | SVG · PNG · JPG · WebP · Canvas · Blob | PNG, JPG (via canvas.toDataURL) |
| Runtime dependencies | 0 | 0 |
| Approach | Clone + foreignObject (browser renders) | Re-implements CSS painter |
| License | MIT | MIT |
Run the live benchmark suite locally with npm run test:benchmark to reproduce the numbers on your hardware — performance varies a lot depending on element complexity, fonts and image content.
When to use which
Choose SnapDOM when…
- You use modern CSS: variables,
::before/::after, gradients, transforms, line-clamp. - Your UI uses Web Components or Shadow DOM.
- You need to embed custom fonts in the output.
- You want to pipe the capture through plugins (filters, watermarks, OCR-friendly output, ASCII, PDF…).
- You care about bundle size and zero dependencies.
html2canvas only makes sense when…
- You're freezing a legacy project on its current dependency tree and won't ship new code against it.
That's a narrow window. The last stable release shipped in January 2022, the README itself recommends against production use, and 1,000+ open issues are sitting unaddressed. For new code, there is no ambiguous case.
Same task, side by side
Capturing #card as a PNG and inserting the resulting image into the page:
html2canvas
import html2canvas from 'html2canvas'; const canvas = await html2canvas(document.querySelector('#card')); const img = new Image(); img.src = canvas.toDataURL('image/png'); 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 ready to be appended — no manual toDataURL, no intermediate canvas. If you do need a canvas, call snapdom.toCanvas(el) instead.
Migration in 5 minutes
The two libraries cover the same surface for the most common operations. Here's the 1:1 mapping:
| html2canvas | SnapDOM |
|---|---|
| html2canvas(el) | snapdom.toCanvas(el) |
| canvas.toDataURL('image/png') | snapdom.toPng(el) |
| canvas.toDataURL('image/jpeg', 0.9) | snapdom.toJpg(el, { quality: 0.9 }) |
| canvas.toBlob(cb, 'image/png') | await snapdom.toBlob(el) |
| { scale: 2 } | { dpr: 2 } |
| { backgroundColor: '#fff' } | { backgroundColor: '#fff' } |
| { ignoreElements: el => … } | { exclude: ['.skip'] } |
| — (not supported) | { embedFonts: true } |
A typical migration boils down to a one-line change:
// Before import html2canvas from 'html2canvas'; const canvas = await html2canvas(el, { scale: 2 }); // After import { snapdom } from '@zumer/snapdom'; const canvas = await snapdom.toCanvas(el, { scale: 2 });
Frequently asked questions
Why does html2canvas not render my Shadow DOM?
html2canvas re-implements the CSS painter and only walks the light DOM, so Shadow DOM trees are skipped entirely — Web Components show up empty in the capture. SnapDOM clones the DOM (including open Shadow roots), inlines the resolved styles, and lets the browser render the result inside an SVG <foreignObject>. Anything the browser paints, SnapDOM captures.
html2canvas doesn't render web fonts correctly, what do I use?
html2canvas relies on whatever the document already loaded; downloaded @font-face files often fall back to system fonts because the canvas painter doesn't see them in time. SnapDOM exposes { embedFonts: true }, which inlines the relevant @font-face declarations into the SVG output so the captured image uses your real fonts.
html2canvas is slow on large DOMs, is there a faster alternative?
html2canvas re-paints every node in JavaScript, which is what makes it slow on dense trees. SnapDOM clones the DOM once and offloads the actual rendering to the browser's native engine via <foreignObject>, which is typically several times faster on the same element. Use the live benchmark above to measure the gap on your own hardware.
Is html2canvas still maintained in 2026?
Practically, no. The last stable release (1.4.1) shipped in January 2022. The repository has more than 1,000 open issues and hasn't produced a published release in over four years. The README still describes the project as "in a very experimental state" and recommends against using it in production. SnapDOM ships regular releases and is actively maintained.
Ready to switch?
Drop SnapDOM into your project, capture your first element, and decide for yourself.
Open the demo Install from npm