SnapDOMGitHub8K
Library Comparison
zumerlab/snapdom

SnapDOM vs modern-screenshot

Two modern libraries, the same core idea: clone the DOM and let the browser render it inside an SVG <foreignObject>. Here's where they actually differ, and how to move between them.

Feature matrix Migration map
TL;DR

Both are actively maintained, fast, zero-dependency, and use the same foreignObject rendering approach, so basic captures look near-identical. modern-screenshot is minimal and TypeScript-first. SnapDOM layers on a plugin system, a broad output API (SVG, PNG, JPG, WebP, Canvas, Blob) and extra fidelity work for tricky CSS, at the same zero-dependency footprint, so the extra capability comes for free. Both are MIT.

SnapDOM vs modern-screenshot, 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.

Test element to be captured by both libraries.
SnapDOM
Waiting to start…
modern-screenshot
Waiting to start…

Same approach, different scope

modern-screenshot and SnapDOM belong to the same family as html-to-image: instead of re-implementing the CSS painter the way html2canvas does, they deep-clone the DOM, inline styles/fonts/images, and wrap the clone in an SVG <foreignObject> so the browser performs the actual render. That shared foundation is why both produce high-fidelity output and run quickly.

The differences are about scope, not approach. modern-screenshot keeps a tight, TypeScript-first surface focused on doing the capture well. SnapDOM aims wider: an extensible plugin pipeline, a larger export API, and a body of workarounds for edge cases (Safari quirks, font embedding, a clone-in-document measurement pass) that show up on complex real-world UIs.

Feature matrix

FeatureSnapDOMmodern-screenshot
Rendering approachClone + foreignObjectClone + foreignObject
Output formatsSVG · PNG · JPG · WebP · Canvas · BlobPNG · JPEG · WebP · SVG · Canvas · Blob
Plugin systemYesNo
Web fonts (@font-face)EmbeddedYes
Pseudo-elementsYesYes
Shadow DOMYesPartial
TypeScript-first APITyped, JS coreYes
Runtime dependencies00
MaintainedActiveActive
LicenseMITMIT

Feature support in other libraries evolves. For anything version-specific, check their docs for the latest. Or run the live benchmark above and compare the output yourself.

When to use which

Choose SnapDOM when…

  • You want to extend capture with plugins (watermarks, filters, custom exporters, PDF…).
  • Your UI uses Shadow DOM / Web Components and you need them captured reliably.
  • You hit edge cases (Safari rendering, complex @font-face, counters, line-clamp) and want them already handled.
  • You want one API spanning SVG, PNG, JPG, WebP, Canvas and Blob.

What a minimal library leaves to you…

  • Watermarks, filters, PDF and custom exports become code you write and maintain yourself.
  • Shadow DOM and Web Component edge cases are yours to debug.
  • Safari rendering quirks and complex @font-face setups need hand-rolled workarounds.
  • Fewer output formats means extra conversion plumbing in your app.

Same task, side by side

Capturing #card as a PNG and inserting the resulting image into the page:

modern-screenshot

import { domToPng } from 'modern-screenshot';

const dataUrl = await domToPng(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);

modern-screenshot's domToPng resolves to a data-URL string; SnapDOM's toPng resolves to a ready-to-append HTMLImageElement. If you want the raw data URL from SnapDOM instead, use snapdom.toRaw(el).

Migration map

The method names map cleanly. The main behavioural difference: SnapDOM's to* helpers return DOM-ready objects (an Image, Canvas or Blob) rather than data-URL strings.

modern-screenshotSnapDOM
domToPng(node)snapdom.toPng(node)
domToJpeg(node)snapdom.toJpg(node)
domToWebp(node)snapdom.toWebp(node)
domToSvg(node)snapdom.toSvg(node)
domToCanvas(node)snapdom.toCanvas(node)
domToBlob(node)snapdom.toBlob(node)

Frequently asked questions

Is SnapDOM or modern-screenshot faster?

They're in the same performance class because both delegate rendering to the browser via foreignObject rather than repainting in JavaScript. Real-world differences come down to element complexity, fonts and images. Benchmark both on your own content rather than trusting a single headline number.

What does SnapDOM offer that modern-screenshot doesn't?

A plugin system for extending the capture pipeline, a broad export API (SVG, PNG, JPG, WebP, Canvas, Blob), reliable Shadow DOM capture, and a set of workarounds for tricky cases like Safari rendering and font embedding. modern-screenshot intentionally keeps a smaller surface area.

Can I migrate from modern-screenshot without rewriting everything?

Mostly yes. The method names map almost 1:1 (see the migration map above). The one adjustment is that SnapDOM returns DOM-ready objects (Image/Canvas/Blob) instead of data-URL strings; use snapdom.toRaw(el) if you specifically need a data URL.

Are both libraries free for commercial use?

Yes. Both SnapDOM and modern-screenshot are MIT-licensed and run entirely client-side, so there's no server cost or per-render fee.

Compare the output

Capture any DOM element to PNG with a single import. No build step, no dependencies.

Open the demo Install from npm