An open API service indexing awesome lists of open source software.

https://github.com/milanofthe/dom2svg

Convert DOM elements to clean, self-contained SVG files.
https://github.com/milanofthe/dom2svg

dom dom-to-svg export html-to-svg javascript react-flow svelteflow svg typescript vector-graphics

Last synced: about 1 month ago
JSON representation

Convert DOM elements to clean, self-contained SVG files.

Awesome Lists containing this project

README

          

# dom2svg

Convert DOM elements to clean, self-contained SVG files.

Built for exporting node-based editors (SvelteFlow, React Flow, etc.) to vector graphics. Handles mixed HTML/SVG structures that existing libraries fail on.

## Demo

A node-based pipeline editor — DOM on the left, exported SVG on the right:

![Pipeline editor export](media/demo-pipeline.png)

Real-world UI components (stat cards, avatars, alerts, buttons, progress bars, charts, palettes, tables, toasts, tags, toggles, breadcrumbs, donut charts, skeleton loaders, keyboard shortcuts):

![Real-world components](media/demo-components.png)

55+ CSS features rendered as DOM elements and their SVG counterparts:

![Feature showcase](media/demo-features.png)

## Install

```bash
npm install github:milanofthe/dom2svg
```

## CLI — Vector Screenshots

Capture any web page as a vector SVG from the command line. Uses headless Chrome via Puppeteer to load the page, then runs dom2svg against it.

Requires Chrome/Chromium installed locally. Puppeteer-core is included as a dev dependency.

```bash
# Full page
npx dom2svg https://example.com -o page.svg

# Specific element with background and padding
npx dom2svg https://news.ycombinator.com -s "#hnmain" -b white -p 12 -o hn.svg

# Pipe to stdout
npx dom2svg https://example.com > page.svg

# Wait for JS-rendered content
npx dom2svg https://app.example.com --wait 3000 -o app.svg

# Custom viewport size
npx dom2svg https://example.com --width 1920 --height 1080 -o wide.svg
```

**Options:**

| Flag | Description | Default |
|------|-------------|---------|
| `-o, --output ` | Output file (default: stdout) | stdout |
| `-s, --selector ` | CSS selector for target element | `body` |
| `-b, --background ` | SVG background color | transparent |
| `-p, --padding ` | Padding around the element | `0` |
| `--width ` | Viewport width | `1280` |
| `--height ` | Viewport height | `800` |
| `--wait ` | Extra wait after page load | `0` |
| `--chrome ` | Chrome executable path | auto-detect |

Chrome is auto-detected on Windows, macOS, and Linux. Override with `--chrome` or the `CHROME_PATH` environment variable.

## Quick Start

```ts
import { domToSvg } from "dom2svg";

const element = document.querySelector("#my-editor");
const result = await domToSvg(element);

// Download as .svg file
result.download("export.svg");
```

## Examples

### Export with white background and padding

```ts
const result = await domToSvg(element, {
background: "#ffffff",
padding: 20,
});
```

### Download or get the SVG as a string/blob

```ts
const result = await domToSvg(element);

// Trigger browser download
result.download("diagram.svg");

// Get SVG markup (e.g. for saving to a server)
const svgString = result.toString();

// Get a Blob (e.g. for FormData upload)
const blob = result.toBlob();
```

### Exclude elements from export

```ts
// By CSS selector
const result = await domToSvg(element, {
exclude: ".toolbar, .minimap, [data-no-export]",
});

// By predicate
const result = await domToSvg(element, {
exclude: (el) => el.tagName === "BUTTON",
});
```

### Text-to-path (font-independent output)

Convert text to `` elements so the SVG renders identically without any fonts installed. Requires [opentype.js](https://github.com/opentypejs/opentype.js) (bundled dependency).

```ts
const result = await domToSvg(element, {
textToPath: true,
fonts: {
// Simple: family → URL
"Inter": "/fonts/Inter-Regular.woff2",

// Detailed: family → config with weight/style
"Inter": {
url: "/fonts/Inter-Bold.woff2",
weight: "700",
style: "normal",
},
},
});
```

### Custom element handler

Override rendering for specific elements:

```ts
const result = await domToSvg(element, {
handler: (el, ctx) => {
// Replace a placeholder with a custom SVG shape
if (el.classList.contains("chart-placeholder")) {
const circle = ctx.svgDocument.createElementNS(
"http://www.w3.org/2000/svg",
"circle",
);
circle.setAttribute("cx", "50");
circle.setAttribute("cy", "50");
circle.setAttribute("r", "40");
circle.setAttribute("fill", "#3b82f6");
return circle;
}
return null; // fall through to default rendering
},
});
```

### Export a SvelteFlow editor

```svelte

import { domToSvg } from "dom2svg";

async function exportDiagram() {
const editor = document.querySelector(".svelte-flow");
const result = await domToSvg(editor, {
background: "#ffffff",
padding: 24,
exclude: ".svelte-flow__controls, .svelte-flow__minimap",
});
result.download("diagram.svg");
}

Export SVG
```

### Export a React Flow editor

```tsx
import { domToSvg } from "dom2svg";

function ExportButton() {
const handleExport = async () => {
const editor = document.querySelector(".react-flow");
const result = await domToSvg(editor, {
background: "#ffffff",
padding: 24,
exclude: ".react-flow__controls, .react-flow__minimap",
});
result.download("flowchart.svg");
};

return Export SVG;
}
```

## API

### `domToSvg(element, options?)`

Converts a DOM element tree into a self-contained SVG.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `element` | `Element` | The root DOM element to convert |
| `options` | `DomToSvgOptions` | Optional configuration (see below) |

**Returns:** `Promise`

| Property | Type | Description |
|----------|------|-------------|
| `svg` | `SVGSVGElement` | The generated SVG element |
| `toString()` | `string` | Serialized SVG with XML declaration |
| `toBlob()` | `Blob` | SVG as `image/svg+xml` blob |
| `download(filename?)` | `void` | Triggers browser download |

### Options

```ts
interface DomToSvgOptions {
/** Background color for the SVG (default: transparent) */
background?: string;

/** Padding around the captured area in px (default: 0) */
padding?: number;

/** CSS selector or predicate to exclude elements */
exclude?: string | ((element: Element) => boolean);

/** Convert text to elements using opentype.js (default: false) */
textToPath?: boolean;

/** Font mapping for text-to-path (family name → URL or config) */
fonts?: Record;

/** Custom element handler — return SVGElement to override, null for default */
handler?: (element: Element, context: RenderContext) => SVGElement | null;
}
```

## Supported CSS Features

| Feature | Support |
|---------|---------|
| **Backgrounds** | |
| Background colors | Full |
| Linear gradients | Full (correct diagonal angles on non-square elements) |
| Radial gradients | Full (circle and ellipse, rasterized via Canvas) |
| Conic gradients | Full (rasterized via Canvas) |
| Multiple backgrounds | Full (layered in correct CSS order) |
| Background size/position | Full (`contain`, `cover`, explicit sizes) |
| Background images (`url()`) | Full (inlined as data URLs) |
| **Borders & Outlines** | |
| Borders (uniform and per-side) | Full (solid, dashed, dotted) |
| Border radius (uniform and non-uniform) | Full (including pill shapes) |
| Outline | Full (solid, dashed, dotted with offset) |
| **Shadows** | |
| Box shadow | Full (outer and inset, multiple, spread, blur) |
| Text shadow | Full (single and multiple, via SVG filters) |
| **CSS Filters** | |
| `blur()` | Full (via `feGaussianBlur`) |
| `brightness()` | Full (via `feComponentTransfer`) |
| `contrast()` | Full (via `feComponentTransfer`) |
| `drop-shadow()` | Full (via `feDropShadow`) |
| `grayscale()` | Full (via `feColorMatrix`) |
| `hue-rotate()` | Full (via `feColorMatrix`) |
| `invert()` | Full (via `feComponentTransfer`) |
| `opacity()` | Full (via `feComponentTransfer`) |
| `saturate()` | Full (via `feColorMatrix`) |
| `sepia()` | Full (via `feColorMatrix`) |
| Filter chaining | Full (multiple filters compose in order) |
| **Layout & Clipping** | |
| CSS transforms | Full (translate, rotate, scale, skew, matrix) |
| Transform origin | Full |
| Opacity | Full |
| Overflow clipping | Full (`hidden`, `clip`, `scroll`, `auto`) |
| `clip-path` | Full (`inset`, `circle`, `ellipse`, `polygon`, `path`) |
| Z-index / stacking contexts | Full (CSS 2.2 paint order) |
| **Elements** | |
| Inline SVGs | Full (deep clone with ID namespacing) |
| `` elements | Full (inlined as data URLs, border-radius clipping) |
| `` elements | Full (via `toDataURL()`) |
| Form elements | Full (``, ``, `` with multiline) |
| Pseudo-elements (`::before`, `::after`) | Full (text content, browser-measured positioning) |
| List markers | Full (disc, circle, square, decimal) |
| **Text** | |
| Text rendering | Full (`` elements, or `` with `textToPath`) |
| Text decoration | Full (underline, line-through) |
| Text transform | Full (uppercase, lowercase, capitalize) |
| Text overflow (ellipsis) | Full (appends ellipsis character) |
| Letter spacing | Full |
| **Visibility** | |
| `visibility: hidden` | Correctly skipped (children still rendered) |
| `display: none` | Correctly skipped |
| `opacity: 0` | Rendered as group with `opacity="0"` (subtree preserved) |

## Architecture

```
src/
├── index.ts # domToSvg() entry point
├── types.ts # All TypeScript interfaces
├── core/
│ ├── traversal.ts # DOM tree walking
│ └── styles.ts # CSS property parsing
├── renderers/
│ ├── html-element.ts # HTML → SVG (backgrounds, borders, overflow, pseudo)
│ ├── svg-element.ts # SVG cloning with ID namespacing
│ └── text-node.ts # Text → or
├── assets/
│ ├── images.ts # Image/canvas → data URL inlining
│ ├── fonts.ts # Font loading + text-to-path (opentype.js)
│ ├── gradients.ts # CSS gradient → SVG gradient / rasterized image
│ ├── filters.ts # CSS filters → SVG filters (blur, grayscale, sepia, etc.)
│ ├── box-shadow.ts # box-shadow → SVG filter / mask
│ ├── clip-path.ts # clip-path → SVG clipPath
│ └── text-shadow.ts # text-shadow → SVG filter
├── transforms/
│ ├── parse.ts # CSS transform string parsing
│ ├── matrix.ts # 2D affine matrix operations
│ └── svg.ts # CSS transform → SVG transform attribute
└── utils/
├── dom.ts # SVG namespace helpers, DOM type guards
├── id-generator.ts # Unique ID generation
└── geometry.ts # Bounding box and rounded-rect path utilities
```

Single runtime dependency: [opentype.js](https://github.com/opentypejs/opentype.js) (only used when `textToPath` is enabled).

The CLI additionally uses [puppeteer-core](https://pptr.dev/) for headless Chrome automation.

## Development

```bash
npm install
npm run build # ESM + CJS + CLI bundles via tsup
npm run type-check # TypeScript strict mode
npm run test # 211 unit tests via Vitest
npm run test:watch # Watch mode
npm run demo # Visual demo at localhost:5173
```

## License

MIT