SnapDOMGitHub8K
SnapDOM Blog

Tiled rasterization for large DOM captures

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.

Priorities for 2027

Faster onboarding, regional growth and durable retention.

One capture · no full-page canvas

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.

GeometryWhat it measuresUse it for
w0 / h0The logical capture boxTile only page content
contentX / contentYIts exact origin inside the viewBoxPlace the first content tile
vbW / vbHThe complete serialized artifactInclude 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.

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.

Capture once. Rasterize in pieces.

toCanvas({ crop }) and immutable capture geometry are available in SnapDOM 2.24.

Read the APIView on GitHub