SnapDOMGitHub8K
Library Comparison
zumerlab/snapdom

SnapDOM vs html2canvas

Compare their rendering approaches, supported features, maintenance status and APIs, then test the migration on your own DOM.

Feature matrix Migration snippet
TL;DR

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.

Test element to be captured by both libraries.
SnapDOM
Waiting to start…
html2canvas
Waiting to start…

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

FeatureSnapDOMhtml2canvas
Pseudo-elements (::before / ::after)FullPartial
Web fonts (@font-face)EmbeddedBest-effort
Shadow DOMYesNo
CSS variablesYesPartial
CSS counter() / counters()YesNo
Same-origin <iframe> captureYesLimited
Line-clamp / multi-line ellipsisYesNo
Plugin systemYesNo
Output formatsSVG · PNG · JPG · WebP · Canvas · BlobPNG, JPG (via canvas.toDataURL)
Runtime dependencies00
ApproachClone + foreignObject (browser renders)Re-implements CSS painter
LicenseMITMIT

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:

html2canvasSnapDOM
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