SnapDOM 2.24 can capture a document once, then rasterize it as a mosaic of small canvases. The important part is when the cut happens: before the browser decodes a single pixel.
Zumerlab · August 11, 2026
TL;DR
A very tall DOM capture can serialize to SVG just fine and still fail when the browser tries to decode that SVG into one enormous bitmap. SnapDOM's new toCanvas({ crop }) windows the SVG before image decode. Capture once, move that window across result.meta, and you can preserve the requested resolution while each tile fits the browser's raster limits—without ever allocating the full-page canvas. It solves the raster ceiling; it does not make cloning an enormous DOM free.
The screenshot was fine. The bitmap wasn't.
There are two very different sizes hiding inside a “full-page screenshot.” The first is the serialized capture: an SVG containing a styled DOM clone inside <foreignObject>. The second is the bitmap a browser creates when that SVG is decoded for PNG, Canvas, JPEG or WebP.
The SVG can describe a page tens of thousands of pixels tall. The bitmap is where browsers push back. A common practical ceiling is 16,384 pixels on one side, with an area limit as well; the exact failure point varies by engine and machine. Go past it and img.decode() may reject with the remarkably unhelpful EncodingError: The source image cannot be decoded, or the canvas allocation may fail later.
SnapDOM already protects ordinary exports from that crash by downscaling oversized raster output. That is the right fallback when the caller asked for one image. It is the wrong trade when the pixels are the product: a deep-zoom viewer, a page renderer, a tiled upload or a print pipeline should not have to throw resolution away simply because one bitmap is the wrong container.
Cut the SVG, not the canvas
The obvious mosaic algorithm is also the broken one: render one giant canvas, then use drawImage() to cut it into smaller canvases. By the time the slicing loop starts, the browser has already had to decode and allocate the giant bitmap. The failure happens before the workaround gets a turn.
toCanvas({ crop }) moves the cut earlier. SnapDOM rewrites the serialized SVG's root width, height and viewBox to describe only the requested window, and then gives it to Image.decode(). The browser never sees a full-height raster source.
Capture onceClone, style and serialize one canonical SVG.
→
Move the windowRewrite the viewBox to one tile's coordinates.
→
Decode smallRasterize only that tile, consume it, repeat.
This work lives entirely in the exporter. The hot capture path still walks and clones the DOM once; asking for ten tiles does not repeat style collection, font embedding or image inlining ten times.
Try the mosaic
The report below is 640 × 2,400 CSS pixels. That is deliberately smaller than a browser limit so the demo stays polite, but it uses the same path as a 40,000-pixel document: one SVG capture, ten 320 × 480 crop windows, ten independent canvases. Scroll the source, then build the mosaic.
Northstar / Annual report 2026
Revenue, retention and regional growth.
One continuous DOM document: metrics, charts, regions and a timeline. The mosaic below never rasterizes it as one continuous bitmap.
Annual revenue$18.4m↑ 24.8%
Active teams12,840↑ 18.1%
Net retention117%↑ 6.2 pts
Performance
Recurring revenue by quarter
Where growth came from
Four regions, four different jobs
North America
Enterprise expansion made existing accounts the largest contributor to net new revenue.
Europe
Localized onboarding shortened time-to-value across the mid-market segment.
Asia Pacific
Partner-led launches opened three markets without adding a regional sales layer.
Latin America
Self-serve adoption doubled after local currency pricing reached the checkout.
The year in four moves
Four releases that moved adoption
New workspace model
Teams could finally organize projects without duplicating permissions and billing.
Usage-based plans
Smaller customers could start earlier and grow without a contract migration.
Regional data storage
European deployments moved from exception handling to the standard path.
Partner API
Implementation partners shipped repeatable integrations instead of one-off scripts.
One capture · no full-page canvas
The gaps, borders and responsive scaling make the tiles visible here. At their native 320 × 480 size with no decoration, the ten canvases reconstruct the source.
The whole implementation is a loop
crop uses SVG viewBox coordinates, so the reliable bounds come from the capture rather than from rereading the live element. This version hands each canvas to a callback immediately; a production exporter can encode, upload or write the tile before requesting the next one.
async function tileCapture(capture, options, consume) {
const {
tileWidth = 2048,
tileHeight = 2048,
scale = 1,
dpr = 1
} = options
const { contentX, contentY, w0, h0 } = capture.meta
for (let y = 0, row = 0; y < h0; y += tileHeight, row++) {
for (let x = 0, col = 0; x < w0; x += tileWidth, col++) {
const width = Math.min(tileWidth, w0 - x)
const height = Math.min(tileHeight, h0 - y)
const crop = {
x: contentX + x,
y: contentY + y,
width,
height
}
const canvas = await capture.toCanvas({ crop, scale, dpr })
await consume({ canvas, crop, row, col })
}
}
}
const capture = await snapdom(document.documentElement, { dpr: 1 })
await tileCapture(capture, {}, async ({ canvas, row, col }) => {
const blob = await new Promise(resolve => canvas.toBlob(resolve, 'image/png'))
if (!blob) throw new Error('PNG encoding failed')
try {
await uploadTile(blob, { row, col })
} finally {
canvas.width = canvas.height = 0
}
})
The exports on one capture are queued, so tiles are rasterized in order instead of all competing for decode and canvas memory. For a visual mosaic like the demo it is fine to retain every canvas. For a genuinely huge page, process a tile and release it before moving on.
The geometry is half the feature
A naïve tiler starts at 0, 0. That only works while the serialized content also starts there. Root transforms, asymmetric shadows, outlines and clip windows can move the logical page inside the SVG viewBox. SnapDOM 2.24 exposes the final, immutable geometry as result.meta, after those effects have been resolved.
Geometry
What it measures
Use it for
w0 / h0
The logical capture box
Tile only page content
contentX / contentY
Its exact origin inside the viewBox
Place the first content tile
vbW / vbH
The complete serialized artifact
Include bleed and outer effects
The loop above tiles the logical page from contentX, contentY over w0 × h0. If the mosaic must preserve every pixel of shadow or transformed bleed, tile from 0, 0 over vbW × vbH instead. Partly outside crop windows are clipped to the viewBox; empty, non-finite or fully outside windows reject rather than quietly returning the whole image.
What tiling does not solve
Tiling removes the full-bitmap allocation from the equation. It does not turn capture into a streaming DOM renderer. SnapDOM still has to clone the complete subtree, read its styles, inline assets and hold the serialized SVG. A 100,000-node document is still a 100,000-node capture.
Virtualized or lazy sections must exist in the DOM before capture; no image library can serialize nodes the application has not rendered.
tileWidth × scale × dpr and tileHeight × scale × dpr still need to fit the browser's raster limits.
Keeping every tile alive eventually holds the same number of pixels in aggregate. Encode or send each tile as you go when memory matters.
Stitching the tiles back into one giant canvas recreates the original problem. The consumer has to understand pages, tiles or levels.
If the initial DOM walk is the bottleneck, clip is the other tool: it prunes off-window subtrees before styling and inlining, but requires a separate capture for each region. crop trades that pruning for one frozen artifact, so fonts, images, animation state and live data cannot change between tiles.
Each crop also decodes and re-encodes the complete SVG data URL, and the browser still parses the full <foreignObject> payload. Tiling bounds bitmap allocation; it does not make SVG string or parse work proportional to the visible tile.
That is a narrower claim than “unlimited screenshots,” and a much more useful one. The DOM is captured once, at one consistent moment. Raster output becomes a sequence of bounded jobs. When each tile fits, no document-wide downscale is required.
Where tile windows are useful
We added crop and immutable geometry for document exporters, where a tall capture naturally becomes PDF pages. The same mechanism also fits map-style viewers, poster printing, zoomable archives and multipart uploads. They all need one stable source and a way to ask for one rectangle at a time.
Plugin authors get the same path through ctx.exports.canvas({ crop }), a silent core-export facade that reuses the canonical capture without recursively firing export hooks. That makes “mosaic” a small custom export instead of a second rendering engine—and keeps the extra work out of SnapDOM's capture hot path.