elementTreeToSvg()
Convert a captured tree into a self-contained SVG string.
Pure function: takes a captured element tree (no Playwright, no I/O) and
returns the SVG markup. Always paired with
captureElementTree().
Signature
function elementTreeToSvg( elements: CapturedElement[], width: number, height: number, idPrefix?: string, includeGlyphDefs?: boolean, ): string
Parameters
captureElementTree (or one you've programmatically built / mutated).viewBox and width attribute. Content beyond this is clipped.viewBox and height attribute."")true)true, the returned markup includes a <defs> with every glyph path used in this SVG. Set to false when you'll hoist shared glyph defs to an outer SVG (e.g. a multi-frame animated SVG).Returns
An SVG fragment: a <defs> block (when defs are
present) followed by the rendered element groups. The function does
not wrap the output in an outer <svg> root
or an XML declaration — that's left to the caller, because Domotion's
own animated-SVG composer
(generateAnimatedSvg())
needs to combine fragments from multiple captures into one root SVG.
To get a standalone, browser-loadable SVG, wrap the fragment with the
wrapSvg() helper:
import { elementTreeToSvg, wrapSvg } from "domotion";
const svg = wrapSvg(elementTreeToSvg(tree, w, h), w, h);
Then run it through
optimizeSvg() for production
size. The output is human-readable and diff-friendly.
wrapSvg(inner, width, height)
One-liner that wraps an inner SVG fragment in the standard
<svg xmlns="..." viewBox="0 0 W H" width="W" height="H">...</svg>
shell. Use it for every standalone capture; generateAnimatedSvg()
already handles wrapping internally.
Examples
Standalone capture
const tree = await captureElementTree(page, "body", vp);
const svg = elementTreeToSvg(tree, vp.width, vp.height);
writeFileSync("capture.svg", svg);
Multiple frames sharing one SVG
const frames = [];
for (let i = 0; i < 3; i++) {
await page.setContent(buildHtml(i));
const tree = await captureElementTree(page, "body", vp);
const svgContent = elementTreeToSvg(
tree, vp.width, vp.height,
`f${i}-`, // idPrefix prevents ID collisions across frames
);
frames.push({ svgContent, duration: 1500 });
}
const animated = generateAnimatedSvg({ width: vp.width, height: vp.height, frames });
Pitfalls
- Forgetting
idPrefixfor multi-frame captures. Without unique prefixes, two frames will both define<clipPath id="clip0">and the SVG will render the wrong geometry on later frames. - Mismatch between viewport and SVG size. The renderer
paints at the captured coordinates and clips at
width/height. To fit a smaller region, capture with a tight viewport box first; don't try to scale at the SVG level.