mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmWeb Frontendupdated 08 Aug 2026

html-to-image

html-to-image turns a live browser DOM node into an SVG data URL, PNG, JPEG, Blob, Canvas, or raw RGBA bytes. It clones the node, copies computed styles and pseudo-elements, embeds fonts and images, serializes the clone inside an SVG foreignObject, then optionally paints that SVG to a canvas. The appeal is a small, dependency-free API for share cards, exports, and visual snapshots when browser rendering is good enough.

Verdict

A useful small browser utility for controlled, same-origin UI exports. Do not install it as a general screenshot engine for arbitrary pages, huge documents, or exact cross-browser output.

API stability4/5The six top-level conversion functions share one stable `(node, options)` promise shape, and version 1.11.13 has no dependency graph that can change behavior underneath it. The options surface has grown without replacing the core calls. Stability is limited by browser SVG, CSS, font, canvas, and security behavior, which the library cannot fully normalize.
Docs4/5The README demonstrates every output function and documents filters, dimensions, quality, cache behavior, pixel ratio, font embedding, scaling, browser requirements, the internal rendering steps, tainted canvas behavior, and data-URI limits. It is practical and honest, though it is one long page and does not provide a compatibility matrix for CSS features or framework-specific lifecycle timing.
Maintenance3/5The latest npm release, 1.11.13, was published on February 14, 2025, while the repository was pushed on May 28, 2026, so source activity is newer than the package. GitHub reports 202 open issues and pull requests, a sizable backlog for a focused utility. The repository is active and not archived, but difficult browser rendering cases may wait.
Ecosystem4/5The package recorded 5,810,617 npm downloads in the latest complete week, has 7,210 GitHub stars, exposes both ESM and CommonJS usage in its README, and adds no runtime dependencies. It works with any framework that can supply a DOM node. Its ecosystem boundary is deliberate: it does not solve server rendering, PDF layout, storage, downloads, or browser automation.

Use it if

  • You need a user-triggered PNG or JPEG export of a reasonably sized DOM subtree in a modern browser
  • You want to filter private controls, apply export-only styles, or set output dimensions without changing the live node
  • You need an SVG data URL, Blob, Canvas, or raw pixels from the same capture flow
  • Your captured assets have compatible CORS rules or can be served from the same origin
Skip it if

Setup reality

`npm install html-to-image` is the whole package install: version 1.11.13 has no runtime or peer dependencies and ships TypeScript code and module entry points. The hard part is preparing the page for capture. Call it only after fonts and images have loaded, and use a real HTMLElement reference rather than a selector that may return null. The library fetches and embeds web fonts, `<img>` sources, and CSS background images. Same-origin assets usually work; cross-origin assets need permissive CORS headers, and any tainted canvas can make canvas export fail. `cacheBust` can avoid stale asset responses but also changes requests. A failed image becomes an empty area unless you provide `imagePlaceholder`. Device pixel ratio affects output size and memory; set `pixelRatio: 1` for predictable dimensions or choose an explicit scale. Very large nodes can exceed data-URI or canvas limits even when `skipAutoScale` is false. Filters exclude a node and all of its descendants and are not called for the root. Repeated captures should reuse `getFontEmbedCSS()` through `fontEmbedCSS` to avoid downloading and parsing the same fonts every time. Because the capture is asynchronous, disable duplicate export clicks, catch failures, and revoke any object URLs you create from Blobs. Treat the result as a browser rendering, not a print engine with pagination or CSS fidelity guarantees.

Patterns

Capture a node and download a PNGdownload-png

import { toPng } from 'html-to-image';

const node = document.querySelector('#share-card');
if (!(node instanceof HTMLElement)) throw new Error('share card missing');

const dataUrl = await toPng(node, { cacheBust: true });
const link = document.createElement('a');
link.download = 'share-card.png';
link.href = dataUrl;
link.click();

Wait for the node's fonts and images before capturing. `cacheBust` changes asset URLs and can trigger new network requests.

Export a compressed JPEG with a backgroundexport-jpeg

import { toJpeg } from 'html-to-image';

const dataUrl = await toJpeg(node, {
  quality: 0.9,
  backgroundColor: '#ffffff',
  pixelRatio: 2,
});

JPEG has no transparency, so set a background color explicitly. Higher pixel ratios increase both sharpness and memory use.

Create and clean up a Blob URLcreate-blob-url

import { toBlob } from 'html-to-image';

const blob = await toBlob(node, { pixelRatio: 1 });
if (!blob) throw new Error('capture produced no blob');
const url = URL.createObjectURL(blob);
preview.src = url;
preview.addEventListener('load', () => URL.revokeObjectURL(url), { once: true });

`toBlob` can produce null when canvas encoding fails. Revoke object URLs after use to release browser memory.

Exclude controls and private contentfilter-elements

const dataUrl = await toPng(node, {
  filter: (child) => !child.classList?.contains('exclude-from-export'),
});

Rejecting one node also rejects its descendants, and the filter is not called for the root node.

Apply styles only to the captured cloneapply-export-style

const dataUrl = await toPng(node, {
  backgroundColor: 'white',
  width: 1200,
  height: 630,
  style: {
    margin: '0',
    transform: 'none',
  },
});

The style object is copied to the cloned root. Width and height change the cloned node, while canvasWidth and canvasHeight scale the full rendered result.

Reuse embedded font CSS across capturesreuse-font-css

import { getFontEmbedCSS, toSvg } from 'html-to-image';

const fontEmbedCSS = await getFontEmbedCSS(firstCard);
const first = await toSvg(firstCard, { fontEmbedCSS });
const second = await toSvg(secondCard, { fontEmbedCSS });

Reuse this only when the captured nodes need the same fonts. It avoids repeating font discovery, downloads, and base64 encoding.

Embed only WOFF2 font sourceschoose-font-format

const dataUrl = await toPng(node, {
  preferredFontFormat: 'woff2',
});

Without this option, every source format in a matching `@font-face` rule may be downloaded and embedded.

Provide a placeholder for failed imageshandle-missing-images

const transparentPixel = 'data:image/gif;base64,R0lGODlhAQABAAAAACw=';
const dataUrl = await toPng(node, {
  imagePlaceholder: transparentPixel,
});

The default placeholder is empty, which leaves blank areas. A placeholder does not bypass CORS restrictions for assets that did load into a tainted canvas.

Get a canvas for further processingrender-to-canvas

import { toCanvas } from 'html-to-image';

const canvas = await toCanvas(node, { pixelRatio: 1 });
const context = canvas.getContext('2d');
if (!context) throw new Error('2D canvas unavailable');
context.fillStyle = '#111827';
context.fillText('Generated in browser', 16, canvas.height - 16);

Drawing or exporting can still throw if cross-origin content has tainted the canvas.

Read RGBA bytes from the captureinspect-pixels

import { toPixelData } from 'html-to-image';

const width = node.scrollWidth;
const pixels = await toPixelData(node, { pixelRatio: 1 });
const x = 10;
const y = 20;
const offset = 4 * (y * width + x);
const rgba = pixels.slice(offset, offset + 4);

Each pixel occupies four bytes in RGBA order. Keep `pixelRatio: 1` or calculate against the scaled output dimensions.

Capture a React ref after a button clickcapture-react-ref

const cardRef = useRef(null);
const [busy, setBusy] = useState(false);

async function exportCard() {
  if (!cardRef.current || busy) return;
  setBusy(true);
  try {
    const url = await toPng(cardRef.current, { cacheBust: true });
    saveDataUrl(url);
  } finally {
    setBusy(false);
  }
}

Guard a null ref and duplicate clicks. State changes made immediately before capture may need a render frame before the DOM reflects them.

Copy only selected computed styleslimit-style-work

const dataUrl = await toPng(node, {
  includeStyleProperties: [
    'background', 'color', 'display', 'font', 'height',
    'margin', 'padding', 'transform', 'width',
  ],
});

This can speed up large captures, but omitted properties disappear from the clone. Test the final list against every component included in the export.

Alternatives

PackageRegistryPick it when
html2canvasnpmYou prefer a canvas-oriented renderer with a long history and can accept its own CSS support limits
dom-to-image-morenpmYou need a maintained continuation of the older dom-to-image API for an existing integration
modern-screenshotnpmYou want another foreignObject-based implementation with newer capture options to compare on difficult pages