SnapDOMGitHub★ 8K
How-To Recipe

Capture with html-in-canvas

Chrome 157 can paint a DOM element into a canvas with one call, drawElementImage(). SnapDOM uses that call as its second engine. Here is how to turn it on, when it is the better engine, and what SnapDOM still does for you around it.

TL;DR

snapdom(el, { engine: 'html-in-canvas' }). In Chrome 157 and newer the capture is painted by Chrome's own renderer, so scrollbars and native controls come out as the page draws them, with the exceptions under Limits. In Firefox, Safari, older Chrome, and on any capture the engine cannot paint, SnapDOM runs the SVG engine on the same clone and you get the same result object. Keep the default for speed; pick this engine when scrollbars, or anything else the SVG image draws differently, have to match the page.

Turn it on

One option. Everything else, the exports, the plugins and the result object, is what you already use.

import { snapdom } from '@zumer/snapdom'

const result = await snapdom(document.querySelector('#card'), { engine: 'html-in-canvas' })
const png = await result.toPng()
document.body.appendChild(png)

A native capture is a bitmap, so its raw form is a PNG data URL; an SVG capture is an SVG one. That is also how you tell which engine ran:

const native = result.toRaw().startsWith('data:image/png')
console.log(native ? 'painted by Chrome' : 'SVG engine')

There is nothing to feature-detect. Set the option everywhere and let each browser do what it can.

What Chrome 157 gives you

html-in-canvas is a WICG specification that Chrome ships without a flag since version 157. You mark a canvas as content="drawable", put the element inside it with the drawable attribute, and once the browser has painted it, draw it into the 2D context:

<canvas id="c" content="drawable" width="400" height="200">
  <div id="card" drawable>…</div>
</canvas>

<script>
  const ctx = c.getContext('2d')
  requestAnimationFrame(() => ctx.drawElementImage(card, 0, 0))
</script>

The browser paints with its own renderer, so everything it can draw on a page it can draw into the canvas, native controls included. Two things shape the design. The element must be a descendant of that canvas, so the DOM is built around the capture. And the paint is "read-back allowed": content the page is not allowed to read, such as a cross-origin image without CORS headers or a url() reference to one, is left out of the picture instead of tainting the canvas.

What SnapDOM adds around that call

SnapDOM does not replace the native paint. It prepares what gets painted and takes care of what comes after.

  1. Capture any element where it is

    The native call draws only an element that lives inside a drawable canvas. SnapDOM clones the element you point at, resolves its computed styles, turns pseudo-elements into real nodes, inlines images and fonts, and mounts that finished clone into a canvas of its own behind the page. Your DOM does not change and your layout does not know a capture happened.

  2. The same call in every browser

    Firefox and Safari have no html-in-canvas. There, and in Chrome before 157, the same option gives you the SVG engine, with the same result object and the same exports. One code path, no feature check, no second library for the browsers that are not Chrome.

  3. What the native paint leaves out, the clone carries

    By the time the engine draws, the clone holds its images and fonts as data URLs, so a cross-origin image the page can read through CORS is in the picture instead of being withheld. An image no script can read still needs a header or a proxy; that guide covers it.

  4. Everything before the paint still happens

    exclude and filter, password masking, plugins that edit the clone (redact fields, replace text, stamp a timestamp), shadow DOM, iframes, lazy image resolution: the native paint sees the finished clone, not the raw page.

  5. Everything after the paint too

    toPng(), toJpg(), toWebp(), toBlob() and download() at your scale and dpr; GIF, video and live-stream plugins frame by frame; agent context. The painted bitmap goes straight to the pixel exports, so nothing is PNG-encoded until you ask for a string.

  6. A capture the engine cannot paint still comes out

    Captures of body or html, transformed or zoomed roots, clip, width/height, outerShadows and plugins with render hooks go through the SVG engine on their own. You never get an error where the native API would have given you an empty canvas.

Which engine, when

SVG stays the default because it is faster. The native engine waits for a rendering update before it can draw, which costs about a frame per capture; the SVG engine serializes the clone and draws it as an image. These are warm recaptures from BENCHMARKS.md, Chrome on an Apple M5, DPR 1:

SVG engine (default)html-in-canvas
BrowsersAllChrome 157 and newer; SVG elsewhere
Form controls, scrollbars, text shapingDrawn as an SVG image, close to the pageThe page's own renderer
Card, 480×240, to canvas2.7 ms35.5 ms
Table, 250 rows, to canvas52.6 ms70.9 ms
Unchanged element, repeated captureUnder 1 msUnder 1 ms
SVG output: toSvg() as SVG, html-export, vector, PDF text layerYesNo, it is a bitmap; those plugins switch the capture to SVG
Full page, clip, transformed rootYesThe SVG engine runs

Pick html-in-canvas for a settings panel or a data grid with its own scrollbars, anything where a native part drawn slightly differently would be noticed. Keep the default for dashboards, cards and social images, where speed matters more and the SVG engine already matches the page.

Both engines on this page

Checking this browser…

Live element

Settings

Invoice #1041
Invoice #1040
Invoice #1039
Invoice #1038
Invoice #1037
SVG engine
Not captured yet

html-in-canvas
Not captured yet

Scroll the invoice list, change the plan, then capture again: both columns come from the same clone, and only the painter differs. The second button runs a tiny plugin that edits the clone before the paint, to show that the native engine draws the prepared clone, not the live element. In a browser without the API both columns say so and come from the SVG engine. The lab runs every official plugin through this engine and reports which ones use SVG.

Limits

The engine paints the clone SnapDOM built, so a control the clone already draws differently from the page looks the same as with the SVG engine: <audio controls> becomes a drawn player, and a bare <progress> or <meter> loses Chrome's native look. Selects, date, range, color and number inputs, checkboxes, radios and buttons match the page with either engine.

The engine paints one plain element box. It does not do document roots, transformed or zoomed ancestors, bleed for outer shadows, region clips or capture-level sizing; those captures take the SVG engine, where that geometry lives. A plugin with a beforeRender or afterRender hook makes the capture use SVG too, because it works at that boundary: html-export, pdf, vector, and redact-inputs with blocks or attributes. A paint that comes back blank or unreadable also falls back, so a successful native capture is always a readable bitmap.

Chromium browsers still on the origin-trial builds (Chrome 148 to 156 with the flag, and forks on those versions) want the older layoutsubtree canvas attribute; SnapDOM sets both names, so they work too.

Frequently asked questions

Do I need a feature check before setting engine: 'html-in-canvas'?

No. In a browser without the API, and on any capture the native engine cannot paint, SnapDOM runs the SVG engine on the same clone and returns the same result object.

Is html-in-canvas faster than the SVG engine?

Not today. The native paint waits for a rendering update, about 35 ms on a small card where the SVG engine takes 3. Repeated captures of an unchanged element are served from memory on both engines.

Does it work in Firefox or Safari?

They have no implementation of html-in-canvas. The same call gives you the SVG engine there, with the same result object.

Can I get an SVG from an html-in-canvas capture?

No. A native capture is a bitmap: url and toRaw() give a PNG data URL, toSvg() returns a PNG-backed image, and an SVG Blob request rejects. Plugins that need the SVG, such as pdf, vector and html-export, make the capture use the SVG engine on their own.

Capture, modify and convert any element

Install SnapDOM and export the DOM to PNG, SVG, PDF or any other format.

Open the demoInstallation