html2canvas implements its own CSS painter and has not shipped a stable release since 2022. SnapDOM uses the browser's SVG renderer, supports open Shadow DOM and plugins, and ships regular releases. Both are MIT-licensed and run in the browser.
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.
Keep html2canvas when…
- An existing project already depends on its rendering behaviour.
- Your supported CSS has been tested and produces the output you need.
For new work, compare both libraries against the CSS and browser versions your product actually uses.
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, { type: 'png' }) |
| { 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, { dpr: 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?
The latest stable release (1.4.1) shipped in January 2022. The repository remains accessible and receives issue activity, but it has not published another stable release. Its README still describes the project as experimental and recommends against production use.
Test the migration
Drop SnapDOM into your project, capture your first element, and decide for yourself.
Open the demo Install from npm