[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-72627":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":10,"rankLanguage":10,"license":22,"archived":23,"fork":23,"defaultBranch":24,"hasWiki":23,"hasPages":25,"topics":26,"createdAt":10,"pushedAt":10,"updatedAt":32,"readmeContent":33,"aiSummary":34,"trendingCount":16,"starSnapshotCount":16,"syncStatus":35,"lastSyncTime":36,"discoverSource":37},72627,"snapdom","zumerlab\u002Fsnapdom","zumerlab","High-performance engine for capturing, modifying, and converting DOM elements into any format.","https:\u002F\u002Fsnapdom.dev",null,"JavaScript",7837,283,21,6,0,4,22,77,12,38.36,"MIT License",false,"main",true,[27,28,29,30,31],"capture-screenshots","clone","dom","js","screenshot","2026-06-12 02:03:05","\u003Cp align=\"center\">\n  \u003Ca href=\"http:\u002F\u002Fzumerlab.github.io\u002Fsnapdom\">\n    \u003Cimg src=\"https:\u002F\u002Fraw.githubusercontent.com\u002Fzumerlab\u002Fsnapdom\u002Fmain\u002Fdocs\u002Fassets\u002Fnewhero.png\" width=\"80%\">\n  \u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">\n \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@zumer\u002Fsnapdom\">\n    \u003Cimg alt=\"NPM version\" src=\"https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002F@zumer\u002Fsnapdom?style=flat-square&label=Version\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@zumer\u002Fsnapdom\">\n    \u003Cimg alt=\"NPM weekly downloads\" src=\"https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fdw\u002F@zumer\u002Fsnapdom?style=flat-square&label=Downloads\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fgraphs\u002Fcontributors\">\n    \u003Cimg alt=\"GitHub contributors\" src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fcontributors\u002Fzumerlab\u002Fsnapdom?style=flat-square&label=Contributors\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fstargazers\">\n    \u003Cimg alt=\"GitHub stars\" src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fstars\u002Fzumerlab\u002Fsnapdom?style=flat-square&label=Stars\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fnetwork\u002Fmembers\">\n    \u003Cimg alt=\"GitHub forks\" src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fforks\u002Fzumerlab\u002Fsnapdom?style=flat-square&label=Forks\">\n  \u003C\u002Fa>\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fsponsors\u002Ftinchox5\">\n    \u003Cimg alt=\"Sponsor tinchox5\" src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fsponsors\u002Ftinchox5?style=flat-square&label=Sponsor\">\n  \u003C\u002Fa>\n\n  \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fblob\u002Fmain\u002FLICENSE\">\n    \u003Cimg alt=\"License\" src=\"https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Flicense\u002Fzumerlab\u002Fsnapdom?style=flat-square\">\n  \u003C\u002Fa>\n\u003C\u002Fp>\n\n\u003Cp align=\"center\">English | \u003Ca href=\"README_CN.md\">简体中文\u003C\u002Fa>\u003C\u002Fp>\n\n# SnapDOM\n\n**SnapDOM** is a next-generation **DOM Capture Engine** — ultra-fast, modular, and extensible.  \nIt converts any DOM subtree into a self-contained representation that can be exported to SVG, PNG, JPG, WebP, Canvas, Blob, or **any custom format** through plugins.\n\n* Full DOM capture\n* Embedded styles, pseudo-elements, and fonts\n* Export to SVG, PNG, JPG, WebP, `canvas`, or Blob\n* ⚡ Ultra fast, no dependencies\n* 100% based on standard Web APIs\n* Support same-origin `ìframe`\n* Support CSS counter() and CSS counters()\n* Support `...` line-clamp\n\n## Demo\n\n[https:\u002F\u002Fsnapdom.dev](https:\u002F\u002Fsnapdom.dev)\n\n\n## Quick Start\n\n**Capture any DOM element to PNG in one line:**\n\n```js\nimport { snapdom } from '@zumer\u002Fsnapdom';\n\nconst img = await snapdom.toPng(document.querySelector('#card'));\ndocument.body.appendChild(img);\n```\n\n**Reusable capture** (one clone, multiple exports):\n\n```js\nconst result = await snapdom(document.querySelector('#card'));\nawait result.toPng();      \u002F\u002F → HTMLImageElement\nawait result.toSvg();      \u002F\u002F → SVG as Image\nawait result.download({ format: 'jpg', filename: 'card.jpg' });\n```\n\n---\n\n## Capture Flow\n\nSnapDOM transforms your DOM element through these stages:\n\n```\nDOM Element\n    ↓\nClone\n    ↓\nStyles & Pseudo\n    ↓\nImages & Backgrounds\n    ↓\nFonts\n    ↓\nSVG foreignObject\n    ↓\ndata:image\u002Fsvg+xml\n    ↓\ntoPng \u002F toSvg \u002F toBlob \u002F download\n```\n\n| Stage | What happens |\n|-------|--------------|\n| **Clone** | Deep clone with styles, Shadow DOM, iframes. Exclude\u002Ffilter nodes. |\n| **Styles & Pseudo** | Inline `::before`\u002F`::after` as elements, resolve `counter()`\u002F`counters()`. |\n| **Images & Backgrounds** | Fetch and inline external images\u002Fbackgrounds as data URLs. |\n| **Fonts** | Embed `@font-face` (optional) and icon fonts. |\n| **SVG** | Wrap clone in `\u003CforeignObject>`, serialize to `data:image\u002Fsvg+xml`. |\n| **Export** | Convert SVG to PNG\u002FJPG\u002FWebP\u002FBlob or trigger download. |\n\nPlugin hooks: `beforeSnap` → `beforeClone` → `afterClone` → `beforeRender` → `afterRender` → `beforeExport` → `afterExport`.\n\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Capture Flow](#capture-flow)\n- [Installation](#installation)\n  - [NPM \u002F Yarn (stable)](#npm--yarn-stable)\n  - [NPM \u002F Yarn (dev builds)](#npm--yarn-dev-builds)\n  - [CDN (stable)](#cdn-stable)\n  - [CDN (dev builds)](#cdn-dev-builds)\n- [Build Outputs](#build-outputs--tree-shaking)\n- [Usage](#usage)\n  - [Reusable capture](#reusable-capture)\n  - [One-step shortcuts](#one-step-shortcuts)\n- [API](#api)\n  - [snapdom(el, options?)](#snapdomel-options)\n  - [Shortcut methods](#shortcut-methods)\n- [Options](#options)\n  - [debug](#debug)\n  - [Fallback image on `\u003Cimg>` load failure](#fallback-image-on-img-load-failure)\n  - [Dimensions (`scale`, `width`, `height`)](#dimensions-scale-width-height)\n  - [Cross-Origin Images & Fonts (`useProxy`)](#cross-origin-images--fonts-useproxy)\n  - [Fonts](#fonts)\n    - [embedFonts](#embedfonts)\n    - [localFonts](#localfonts)\n    - [iconFonts](#iconfonts)\n    - [excludeFonts](#excludefonts)\n  - [Filtering nodes: `exclude` vs `filter`](#filtering-nodes-exclude-vs-filter)\n  - [outerTransforms](#outerTransforms)\n  - [outerShadows](#no-shadows)\n  - [Cache control](#cache-control)\n- [preCache](#precache--optional-helper)\n- [Plugins (BETA)](#plugins-beta)\n  - [Official Plugins](#official-plugins)\n  - [Community Plugins](#community-plugins)\n  - [Build a Plugin in 5 Minutes](#build-a-plugin-in-5-minutes)\n  - [Registering Plugins](#registering-plugins)\n  - [Plugin Lifecycle Hooks](#plugin-lifecycle-hooks)\n  - [Context Object](#context-object)\n  - [Custom Exports via Plugins](#custom-exports-via-plugins)\n  - [Example: Overlay Filter Plugin](#example-overlay-filter-plugin)\n  - [Full Plugin Template](#full-plugin-template)\n- [Limitations](#limitations)\n- [⚡ Performance Benchmarks (Chromium)](#performance-benchmarks)\n  - [Simple elements](#simple-elements)\n  - [Complex elements](#complex-elements)\n  - [Run the benchmarks](#run-the-benchmarks)\n- [Roadmap](#roadmap)\n- [Development](#development)\n- [Contributors 🙌](#contributors)\n- [💖 Sponsors](#sponsors)\n- [Star History](#star-history)\n- [License](#license)\n\n\n\n## Installation\n\n### NPM \u002F Yarn (stable)\n\n```bash\nnpm i @zumer\u002Fsnapdom\nyarn add @zumer\u002Fsnapdom\n```\n\n### NPM \u002F Yarn (dev builds)\n\nFor early access to new features and fixes:\n\n```bash\nnpm i @zumer\u002Fsnapdom@dev\nyarn add @zumer\u002Fsnapdom@dev\n```\n\n⚠️ The `@dev` tag usually includes improvements before they reach production, but may be less stable.\n\n\n### CDN (stable)\n\n```html\n\u003C!-- Minified build -->\n\u003Cscript src=\"https:\u002F\u002Funpkg.com\u002F@zumer\u002Fsnapdom\u002Fdist\u002Fsnapdom.js\">\u003C\u002Fscript>\n\n\u003C!-- Minified ES Module build -->\n\u003Cscript type=\"module\">\n  import { snapdom } from \"https:\u002F\u002Funpkg.com\u002F@zumer\u002Fsnapdom\u002Fdist\u002Fsnapdom.mjs\";\n\u003C\u002Fscript>\n```\n\n### CDN (dev builds)\n\n```html\n\u003C!-- Minified build (dev) -->\n\u003Cscript src=\"https:\u002F\u002Funpkg.com\u002F@zumer\u002Fsnapdom@dev\u002Fdist\u002Fsnapdom.js\">\u003C\u002Fscript>\n\n\u003C!-- Minified ES Module build (dev) -->\n\u003Cscript type=\"module\">\n  import { snapdom } from \"https:\u002F\u002Funpkg.com\u002F@zumer\u002Fsnapdom@dev\u002Fdist\u002Fsnapdom.mjs\";\n\u003C\u002Fscript>\n```\n\n## Build Outputs\n\n| Variant | File | Use case |\n|---------|------|----------|\n| **ESM** (tree-shakeable) | `dist\u002Fsnapdom.mjs` | Bundlers (Vite, webpack), `import` |\n| **IIFE** (global) | `dist\u002Fsnapdom.js` | Script tag, legacy `require` |\n\n**Bundler (npm):**\n```js\nimport { snapdom } from '@zumer\u002Fsnapdom';  \u002F\u002F → dist\u002Fsnapdom.mjs\n```\n\n**Script tag (CDN):**\n```html\n\u003Cscript src=\"https:\u002F\u002Funpkg.com\u002F@zumer\u002Fsnapdom\u002Fdist\u002Fsnapdom.js\">\u003C\u002Fscript>\n\u003Cscript> snapdom.toPng(document.body).then(img => document.body.appendChild(img)); \u003C\u002Fscript>\n```\n\n**Subpath imports** (lighter bundle if you only need one):\n```js\nimport { preCache } from '@zumer\u002Fsnapdom\u002FpreCache';\nimport { plugins } from '@zumer\u002Fsnapdom\u002Fplugins';\n```\n\n\n## Usage\n\n| Pattern | When to use |\n|---------|-------------|\n| **Reusable** `snapdom(el)` | One clone → many exports (PNG + JPG + download). |\n| **Shortcuts** `snapdom.toPng(el)` | Single export, less code. |\n\n### Reusable capture\n\nCapture once, export many times (no re-clone):\n\n```js\nconst el = document.querySelector('#target');\nconst result = await snapdom(el);\n\nconst img = await result.toPng();\ndocument.body.appendChild(img);\nawait result.download({ format: 'jpg', filename: 'my-capture.jpg' });\n```\n\n### One-step shortcuts\n\nDirect export when you need a single format:\n\n```js\nconst png = await snapdom.toPng(el);\nconst blob = await snapdom.toBlob(el);\ndocument.body.appendChild(png);\n```\n\n## API\n\n### `snapdom(el, options?)`\n\nReturns an object with reusable export methods:\n\n```js\n{\n  url: string;\n  toRaw(): string;\n  toImg(): Promise\u003CHTMLImageElement>; \u002F\u002F deprecated \n  toSvg(): Promise\u003CHTMLImageElement>;\n  toCanvas(): Promise\u003CHTMLCanvasElement>;\n  toBlob(options?): Promise\u003CBlob>;\n  toPng(options?): Promise\u003CHTMLImageElement>;\n  toJpg(options?): Promise\u003CHTMLImageElement>;\n  toWebp(options?): Promise\u003CHTMLImageElement>;\n  download(options?): Promise\u003Cvoid>;\n}\n```\n\n### Shortcut methods\n\n| Method                         | Description                       |\n| ------------------------------ | --------------------------------- |\n| `snapdom.toImg(el, options?)`  | Returns an SVG `HTMLImageElement` (deprecated) |\n| `snapdom.toSvg(el, options?)`  | Returns an SVG `HTMLImageElement` |\n| `snapdom.toCanvas(el, options?)` | Returns a `Canvas`               |\n| `snapdom.toBlob(el, options?)` | Returns an SVG or raster `Blob`   |\n| `snapdom.toPng(el, options?)`  | Returns a PNG image               |\n| `snapdom.toJpg(el, options?)`  | Returns a JPG image               |\n| `snapdom.toWebp(el, options?)` | Returns a WebP image              |\n| `snapdom.download(el, options?)` | Triggers a download              |\n\n### Exporter-specific options\n\nSome exporters accept a small set of **export-only options** in addition to the global capture options.\n\n#### `download()` \n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `filename` | `string` | `snapdom` | Download name. |\n| `format` | `\"png\" \\| \"jpeg\" \\| \"jpg\" \\| \"webp\" \\| \"svg\"` | `\"png\"` | Output format for the downloaded file. |\n\n**Example:**\n\n```js\nawait result.download({\n  format: 'jpg',\n  quality: 0.92,\n  filename: 'my-capture'\n});\n```\n\n#### `toBlob()`\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `type` | `\"svg\" \\| \"png\" \\| \"jpeg\" \\| \"jpg\" \\| \"webp\"` | `\"svg\"` | Blob type to generate. |\n\n**Example:**\n\n```js\nconst blob = await result.toBlob({ type: 'jpeg', quality: 0.92 });\n```\n\n## Options\n\nAll capture methods accept an `options` object:\n\n\n| Option            | Type     | Default  | Description                                     |\n| ----------------- | -------- | -------- | ----------------------------------------------- |\n| `debug`           | boolean  | `false`  | When `true`, logs suppressed errors to `console.warn` for troubleshooting |\n| `fast`            | boolean  | `true`   | Skips small idle delays for faster results      |\n| `embedFonts`      | boolean  | `false`  | Inlines non-icon fonts (icon fonts always on)   |\n| `localFonts`      | array    | `[]`     | Local fonts `{ family, src, weight?, style? }`  |\n| `iconFonts`       | string\\|RegExp\\|Array | `[]` | Extra icon font matchers                      |\n| `excludeFonts`    | object   | `{}`     | Exclude families\u002Fdomains\u002Fsubsets during embedding |\n| `scale`           | number   | `1`      | Output scale multiplier                         |\n| `dpr`             | number   | `devicePixelRatio` | Device pixel ratio                     |\n| `width`           | number   | -        | Output width                                    |\n| `height`          | number   | -        | Output height                                   |\n| `backgroundColor` | string   | `\"#fff\"` | Fallback color for JPG\u002FWebP                     |\n| `quality`         | number   | `1`      | Quality for JPG\u002FWebP (0 to 1)                   |\n| `useProxy`        | string   | `''`     | Proxy base for CORS fallbacks                   |\n| `exclude`         | string[] | -        | CSS selectors to exclude                        |\n| `excludeMode`     | `\"hide\"`\\|`\"remove\"` | `\"hide\"` | How `exclude` is applied                  |\n| `filter`          | function | -        | Custom predicate `(el) => boolean`              |\n| `filterMode`      | `\"hide\"`\\|`\"remove\"` | `\"hide\"` | How `filter` is applied                   |\n| `cache`           | string   | `\"soft\"` | `disabled` \\| `soft` \\| `auto` \\| `full`        |\n| `placeholders`    | boolean  | `true`   | Show placeholders for images\u002FCORS iframes       |\n| `fallbackURL`     | string \\| function  | - | Fallback image for `\u003Cimg>` load failure |\n| `outerTransforms`      | boolean  | `true`  | When `false` removes `translate\u002Frotate` but preserves `scale\u002Fskew`, producing a flat, reusable capture |\n| `outerShadows`       | boolean  | `false`  | Do not expand the root’s bounding box for shadows\u002Fblur\u002Foutline, and strip those visual effects from the cloned root |\n| `safariWarmupAttempts` | number   | `3`      | Safari only: iterations to prime font\u002Fdecode (WebKit #219770). Use `1` if 3 causes lag |\n\n### debug\n\nWhen `debug: true`, SnapDOM logs normally suppressed errors to `console.warn` (with the `[snapdom]` prefix). Useful for troubleshooting capture issues (canvas failures, blob resolution, style stripping, etc.) without noisy output in production.\n\n```js\nawait snapdom.toPng(el, { debug: true });\n```\n\n### Fallback image on `\u003Cimg>` load failure\n\nProvide a default image for failed `\u003Cimg>` loads. You can pass a fixed URL or a callback that receives measured dimensions and returns a URL (handy to generate dynamic placeholders).\n\n```js\n\u002F\u002F 1) Fixed URL fallback\nawait snapdom.toSvg(element, {\n  fallbackURL: '\u002Fimages\u002Ffallback.png'\n});\n\n\u002F\u002F 2) Dynamic placeholder via callback\nawait snapdom.toSvg(element, {\n  fallbackURL: ({ width: 300, height: 150 }) =>\n    `https:\u002F\u002Fplacehold.co\u002F${width}x${height}`\n});\n\n\u002F\u002F 3) With proxy (if your fallback host has no CORS)\nawait snapdom.toSvg(element, {\n  fallbackURL: ({ width = 300, height = 150 }) =>\n    `https:\u002F\u002Fdummyimage.com\u002F${width}x${height}\u002Fcccccc\u002F666.png&text=img`,\n  useProxy: 'https:\u002F\u002Fproxy.corsfix.com\u002F?'\n});\n```\n\nNotes:\n- If the fallback image also fails to load, snapDOM replaces the `\u003Cimg>` with a placeholder block preserving width\u002Fheight.\n- Width\u002Fheight used by the callback are gathered from the original element (dataset, style\u002Fattrs, etc.) when available.\n\n\n### Dimensions (`scale`, `width`, `height`)\n\n* If `scale` is provided, it **takes precedence** over `width`\u002F`height`.\n* If only `width` is provided, height scales proportionally (and vice versa).\n* Providing both `width` and `height` forces an exact size (may distort).\n\n### Cross-Origin Images & Fonts (`useProxy`)\n\nBy default snapDOM tries `crossOrigin=\"anonymous\"` (or `use-credentials` for same-origin). If an asset is CORS-blocked, you can set `useProxy` to a prefix URL that forwards the actual `src`:\n\n```js\nawait snapdom.toPng(el, {\n  useProxy: 'https:\u002F\u002Fproxy.corsfix.com\u002F?' \u002F\u002F Note: Any cors proxy could be used 'https:\u002F\u002Fproxy.corsfix.com\u002F?'\n});\n```\n\n\n* The proxy is only used as a **fallback**; same-origin and CORS-enabled assets skip it.\n\n### Fonts\n\n#### `embedFonts`\nWhen `true`, snapDOM embeds **non-icon** `@font-face` rules detected as used within the captured subtree. Icon fonts (Font Awesome, Material Icons, etc.) are embedded **always**.\n\n#### `localFonts`\nIf you serve fonts yourself or have data URLs, you can declare them here to avoid extra CSS discovery:\n\n```js\nawait snapdom.toPng(el, {\n  embedFonts: true,\n  localFonts: [\n    { family: 'Inter', src: '\u002Ffonts\u002FInter-Variable.woff2', weight: 400, style: 'normal' },\n    { family: 'Inter', src: '\u002Ffonts\u002FInter-Italic.woff2', style: 'italic' }\n  ]\n});\n```\n\n#### `iconFonts`\nAdd custom icon families (names or regex matchers). Useful for private icon sets:\n\n```js\nawait snapdom.toPng(el, {\n  iconFonts: ['MyIcons', \u002F^(Remix|Feather) Icons?$\u002Fi]\n});\n```\n\n#### `excludeFonts`\nSkip specific non-icon fonts to speed up capture or avoid unnecessary downloads.\n\n```js\nawait snapdom.toPng(el, {\n  embedFonts: true,\n  excludeFonts: {\n    families: ['Noto Serif', 'SomeHeavyFont'],     \u002F\u002F skip by family name\n    domains: ['fonts.gstatic.com', 'cdn.example'], \u002F\u002F skip by source host\n    subsets: ['cyrillic-ext']                      \u002F\u002F skip by unicode-range subset tag\n  }\n});\n```\n*Notes*\n- `excludeFonts` only applies to **non-icon** fonts. Icon fonts are always embedded.\n- Matching is case-insensitive for `families`. Hosts are matched by substring against the resolved URL.\n\n\n#### Filtering nodes: `exclude` vs `filter`\n\n* `exclude`: remove by **selector**.\n* `excludeMode`: `hide` applies `visibility:hidden` CSS rule on excluded nodes and the layout remains as the original. `remove` do not clone excluded nodes at all.\n* `filter`: advanced predicate per element (return `false` to drop).\n* `filterMode`: `hide` applies `visibility:hidden` CSS rule on filtered nodes and the layout remains as the original. `remove` do not clone filtered nodes at all.\n\n**Example: filter out elements with `display:none`:**\n```js\n\u002F**\n * Example filter: skip elements with display:none\n * @param {Element} el\n * @returns {boolean} true = keep, false = exclude\n *\u002F\nfunction filterHidden(el) {\n  const cs = window.getComputedStyle(el);\n  if (cs.display === 'none') return false;\n  return true;\n}\n\nawait snapdom.toPng(document.body, { filter: filterHidden });\n```\n\n**Example with `exclude`:** remove banners or tooltips by selector\n```js\nawait snapdom.toPng(el, {\n  exclude: ['.cookie-banner', '.tooltip', '[data-test=\"debug\"]']\n});\n```\n\n### outerTransforms \n\nWhen capturing rotated or translated elements, you may want use **outerTransforms: false** option if you want to eliminate those external transforms. So, the output is **flat, upright, and ready** to use elsewhere.\n\n- **`outerTransforms: true (default)`**  \n  **Keeps the original `transforms` and `rotate`**.  \n  \n\n### outerShadows\n- **`outerShadows: false (default)`**  \n  Prevents expanding the bounding box for shadows, blur, or outline on the root, and also strips `box-shadow`, `text-shadow`, `filter: blur()\u002Fdrop-shadow()`, and `outline` from the cloned root.  \n\n> 💡 **Tip:** Using both (`outerTransforms: false` + `outerShadows: false`) produces a strict, minimal bounding box with no visual bleed.\n\n**Example**\n\n```js\n\u002F\u002F outerTransforms and remove shadow bleed\nawait snapdom.toSvg(el, { outerTransforms: true, outerShadows: true });\n```\n\n## Cache control\n\nSnapDOM maintains internal caches for images, backgrounds, resources, styles, and fonts.\nYou can control how they are cleared between captures using the `cache` option:\n\n| Mode        | Description                                                                 |\n| ----------- | --------------------------------------------------------------------------- |\n| `\"disabled\"`| No cache                   |\n| `\"soft\"`    | Clears session caches (`styleMap`, `nodeMap`, `styleCache`) _(default)_      |\n| `\"auto\"`    | Minimal cleanup: only clears transient maps                                 |\n| `\"full\"`    | Keeps all caches (nothing is cleared, maximum performance)                  |\n\n**Examples:**\n\n```js\n\u002F\u002F Use minimal but fast cache\nawait snapdom.toPng(el, { cache: 'auto' });\n\n\u002F\u002F Keep everything in memory between captures\nawait snapdom.toPng(el, { cache: 'full' });\n\n\u002F\u002F Force a full cleanup on every capture\nawait snapdom.toPng(el, { cache: 'disabled' });\n```\n\n## `preCache()` – Optional helper\n\nPreloads external resources to avoid first-capture stalls (helpful for big\u002Fcomplex trees).\n\n```js\nimport { preCache } from '@zumer\u002Fsnapdom';\n\nawait preCache({\n  root: document.body,\n  embedFonts: true,\n  localFonts: [{ family: 'Inter', src: '\u002Ffonts\u002FInter.woff2', weight: 400 }],\n  useProxy: 'https:\u002F\u002Fproxy.corsfix.com\u002F?'\n});\n```\n\n## Plugins (BETA)\n\nSnapDOM includes a lightweight **plugin system** that allows you to extend or override behavior at any stage of the capture and export process — without touching the core library.\n\nA plugin is a simple object with a unique `name` and one or more lifecycle **hooks**.\nHooks can be synchronous or `async`, and they receive a shared **`context`** object.\n\n### Official Plugins\n\nInstall the official plugin package:\n\n```bash\nnpm install @zumer\u002Fsnapdom-plugins\n```\n\n```js\nimport { filter } from '@zumer\u002Fsnapdom-plugins\u002Ffilter';\nimport { timestampOverlay } from '@zumer\u002Fsnapdom-plugins\u002Ftimestamp-overlay';\n```\n\n| Plugin | Category | Description |\n|--------|----------|-------------|\n| `picture-resolver` | Capture | Resolves lazy-loaded `\u003Cpicture>` placeholders. Detects base64 stubs and fetches the real image before capture. |\n| `timestamp-overlay` | Transform | Adds a configurable timestamp label on the captured clone. Supports multiple date formats and positions. |\n| `filter` | Transform | Applies CSS filter effects to captures. Ships with presets: `grayscale`, `sepia`, `blur`, `vintage`, `dramatic`. |\n| `replace-text` | Transform | Find-and-replace text in the captured clone. Supports strings and regex patterns. |\n| `color-tint` | Transform | Tints the entire capture to a specified color using an overlay with `mix-blend-mode`. |\n| `ascii-export` | Export | Adds a `toAscii()` method that converts captures to ASCII art. Configurable width, charset, and luminance. |\n| `pdf-image` | Export | Exports the capture as a PNG embedded in a downloadable PDF. Supports portrait and landscape orientations. |\n| `html-in-canvas` | Export | Uses the experimental WICG `drawElementImage` API for direct DOM-to-canvas rendering where supported. |\n| `prompt-export` | Export | LLM-friendly capture: adds a `toPrompt()` method that returns an annotated screenshot, structured element map with bounding boxes, and a pre-formatted text prompt. |\n\n### Community Plugins\n\nCommunity plugins are listed on the [Plugins page](https:\u002F\u002Fzumerlab.github.io\u002Fsnapdom\u002Fplugins.html). To submit your plugin, open a PR adding one line to `community-plugins.md`. See [CONTRIBUTING_PLUGINS.md](CONTRIBUTING_PLUGINS.md).\n\n### Build a Plugin in 5 Minutes\n\nSnapDOM's hook system gives you full control over every stage of the capture pipeline:\n\n1. **Clone the template** — `npx degit zumerlab\u002Fsnapdom\u002Fpackages\u002Fplugin-template my-plugin`\n2. **Write your hook logic** — `export function myPlugin() {}`\n3. **Get listed** — open a PR adding one line to `community-plugins.md`\n\nSee [PLUGIN_SPEC.md](PLUGIN_SPEC.md) for the full specification and [CONTRIBUTING_PLUGINS.md](CONTRIBUTING_PLUGINS.md) for submission guidelines.\n\n### Registering Plugins\n\n**Global registration** (applies to all captures):\n\n```js\nimport { snapdom } from '@zumer\u002Fsnapdom';\n\n\u002F\u002F You can register instances, factories, or [factory, options]\nsnapdom.plugins(\n  myPluginInstance,\n  [myPluginFactory, { optionA: true }],\n  { plugin: anotherFactory, options: { level: 2 } }\n);\n```\n\n**Per-capture registration** (only for that specific call):\n\n```js\nconst out = await snapdom(element, {\n  plugins: [\n    [overlayFilterPlugin, { color: 'rgba(0,0,0,0.25)' }],\n    [myFullPlugin, { providePdf: true }]\n  ]\n});\n```\n\n* **Execution order = registration order** (first registered, first executed).\n* **Per-capture plugins** run **before** global ones.\n* Duplicates are automatically skipped by `name`; a per-capture plugin with the same `name` overrides its global version.\n\n### Plugin Lifecycle Hooks\n\nHooks run in capture order (see [Capture Flow](#capture-flow)):\n\n| Hook | Stage | Purpose |\n|------|-------|---------|\n| `beforeSnap` | Start | Adjust options before any work. |\n| `beforeClone` | Pre-clone | Before DOM clone (modify live DOM carefully). |\n| `afterClone` | Post-clone | Modify cloned tree safely (e.g. inject overlay). |\n| `beforeRender` | Pre-serialize | Right before SVG → data URL. |\n| `afterRender` | Post-serialize | Inspect `context.svgString` \u002F `context.dataURL`. |\n| `beforeExport` | Per export | Before each `toPng`, `toSvg`, etc. |\n| `afterExport` | Per export | Transform returned result. |\n| `afterSnap` | Once | After first export; cleanup. |\n| `defineExports` | Setup | Add custom exporters (e.g. `toPdf`). |\n\n> Returned values from `afterExport` are chained to the next plugin (transform pipeline).\n\n### Context Object\n\nEvery hook receives a single `context` object that contains normalized capture state:\n\n* **Input & options:**\n  `element`, `debug`, `fast`, `scale`, `dpr`, `width`, `height`, `backgroundColor`, `quality`, `useProxy`, `cache`, `outerTransforms`, `outerShadows`, `safariWarmupAttempts`, `embedFonts`, `localFonts`, `iconFonts`, `excludeFonts`, `exclude`, `excludeMode`, `filter`, `filterMode`, `fallbackURL`.\n\n* **Intermediate values (depending on stage):**\n  `clone`, `classCSS`, `styleCache`, `fontsCSS`, `baseCSS`, `svgString`, `dataURL`.\n\n* **During export:**\n  `context.export = { type, options, url }`\n  where `type` is the exporter name (`\"png\"`, `\"jpeg\"`, `\"svg\"`, `\"blob\"`, etc.), and `url` is the serialized SVG base.\n\n> You may safely modify `context` (e.g., override `backgroundColor` or `quality`) — but do so early (`beforeSnap`) for global effects or in `beforeExport` for single-export changes.\n\n\n## Custom Exports via Plugins\n\nPlugins can add new exports using `defineExports(context)`.\nFor each export key you return (e.g., `\"pdf\"`), SnapDOM automatically exposes a helper method named **`toPdf()`** on the capture result.\n\n**Register the plugin (global or per capture):**\n\n```js\nimport { snapdom } from '@zumer\u002Fsnapdom';\n\n\u002F\u002F global\nsnapdom.plugins(pdfExportPlugin());\n\n\u002F\u002F or per capture\nconst out = await snapdom(element, { plugins: [pdfExportPlugin()] });\n```\n\n**Call the custom export:**\n\n```js\nconst out = await snapdom(document.querySelector('#report'));\n\n\u002F\u002F because the plugin returns { pdf: async (ctx, opts) => ... }\nconst pdfBlob = await out.toPdf({\n  \u002F\u002F exporter-specific options (width, height, quality, filename, etc.)\n});\n```\n\n### Example: Overlay Filter Plugin\n\nAdds a translucent overlay or color filter **only** to the captured clone (not your live DOM).\nUseful for highlighting or dimming sections before export.\n\n```js\n\u002F**\n * Ultra-simple overlay filter for SnapDOM (HTML-only).\n * Inserts a full-size \u003Cdiv> overlay on the cloned root.\n *\n * @param {{ color?: string; blur?: number }} [options]\n *   color: overlay color (rgba\u002Fhex\u002Fhsl). Default: 'rgba(0,0,0,0.25)'\n *   blur: optional blur in px (default: 0)\n *\u002F\nexport function overlayFilterPlugin(options = {}) {\n  const color = options.color ?? 'rgba(0,0,0,0.25)';\n  const blur = Math.max(0, options.blur ?? 0);\n\n  return {\n    name: 'overlay-filter',\n\n    \u002F**\n     * Add a full-coverage overlay to the cloned HTML root.\n     * @param {any} context\n     *\u002F\n    async afterClone(context) {\n      const root = context.clone;\n      if (!(root instanceof HTMLElement)) return; \u002F\u002F HTML-only\n\n      \u002F\u002F Ensure containing block so absolute overlay anchors to the root\n      if (getComputedStyle(root).position === 'static') {\n        root.style.position = 'relative';\n      }\n\n      const overlay = document.createElement('div');\n      overlay.style.position = 'absolute';\n      overlay.style.left = '0';\n      overlay.style.top = '0';\n      overlay.style.right = '0';\n      overlay.style.bottom = '0';\n      overlay.style.background = color;\n      overlay.style.pointerEvents = 'none';\n      if (blur) overlay.style.filter = `blur(${blur}px)`;\n\n      root.appendChild(overlay);\n    }\n  };\n}\n\n```\n\n**Usage:**\n\n```js\nimport { snapdom } from '@zumer\u002Fsnapdom';\n\n\u002F\u002F Global registration\nsnapdom.plugins([overlayFilterPlugin, { color: 'rgba(0,0,0,0.3)', blur: 2 }]);\n\n\u002F\u002F Per-capture\nconst out = await snapdom(document.querySelector('#card'), {\n  plugins: [[overlayFilterPlugin, { color: 'rgba(255,200,0,0.15)' }]]\n});\n\nconst png = await out.toPng();\ndocument.body.appendChild(png);\n```\n\n> The overlay is injected **only in the cloned tree**, never in your live DOM, ensuring perfect fidelity and zero flicker.\n\n\n### Full Plugin Template\n\nUse this as a starting point for custom logic or exporters.\n\n```js\nexport function myPlugin(options = {}) {\n  return {\n    \u002F** Unique name used for de-duplication\u002Foverrides *\u002F\n    name: 'my-plugin',\n\n    \u002F** Early adjustments before any clone\u002Fstyle work. *\u002F\n    async beforeSnap(context) {},\n\n    \u002F** Before subtree cloning (use sparingly if touching the live DOM). *\u002F\n    async beforeClone(context) {},\n\n    \u002F** After subtree cloning (safe to modify the cloned tree). *\u002F\n    async afterClone(context) {},\n\n    \u002F** Right before serialization (SVG\u002FdataURL). *\u002F\n    async beforeRender(context) {},\n\n    \u002F** After serialization; inspect context.svgString\u002Fcontext.dataURL if needed. *\u002F\n    async afterRender(context) {},\n\n    \u002F** Before EACH export call (toPng\u002FtoSvg\u002FtoBlob\u002F...). *\u002F\n    async beforeExport(context) {},\n\n    \u002F**\n     * After EACH export call.\n     * If you return a value, it becomes the result for the next plugin (chaining).\n     *\u002F\n    async afterExport(context, result) { return result; },\n\n    \u002F**\n     * Define custom exporters (auto-added as helpers like out.toPdf()).\n     * Return a map { [key: string]: (ctx:any, opts:any) => Promise\u003Cany> }.\n     *\u002F\n    async defineExports(context) { return {}; },\n\n    \u002F** Runs ONCE after the FIRST export finishes (cleanup). *\u002F\n    async afterSnap(context) {}\n  };\n}\n```\n\n**Quick recap:**\n\n* Plugins can modify capture behavior (`beforeSnap`, `afterClone`, etc.).\n* You can inject visuals or transformations safely into the cloned tree.\n* New exporters defined in `defineExports()` automatically become helpers like `out.toPdf()`.\n* All hooks can be asynchronous, run in order, and share the same `context`.\n\n\n## Limitations\n\n* External images should be CORS-accessible (use `useProxy` option for handling CORS denied)\n* When WebP format is used on Safari, it will fallback to PNG rendering.\n* `@font-face` CSS rule is well supported, but if need to use JS `FontFace()`, see this workaround [`#43`](https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fissues\u002F43)\n* **Safari**: captures with `embedFonts` or background\u002Fmask images run slower due to [WebKit #219770](https:\u002F\u002Fbugs.webkit.org\u002Fshow_bug.cgi?id=219770) (font decode timing). SnapDOM does pre-captures + `drawImage` to prime the pipeline; configurable via `safariWarmupAttempts` (default 3).\n* **Custom scrollbar styles** (`::-webkit-scrollbar`): Applied only when the element has *not* been scrolled. When scrolled, the viewport content is captured without the scrollbar.\n\n\n## Performance Benchmarks\n\n**Setup.** Vitest benchmarks on Chromium, repo tests. Hardware may affect results.\nValues are **average capture time (ms)** → lower is better.\n\n### Simple elements\n\n| Scenario                 | SnapDOM current | SnapDOM v1.9.9 | html2canvas | html-to-image |\n| ------------------------ | --------------- | -------------- | ----------- | ------------- |\n| Small (200×100)          | **0.5 ms**      | 0.8 ms         | 67.7 ms     | 3.1 ms        |\n| Modal (400×300)          | **0.5 ms**      | 0.8 ms         | 75.5 ms     | 3.6 ms        |\n| Page View (1200×800)     | **0.5 ms**      | 0.8 ms         | 114.2 ms    | 3.3 ms        |\n| Large Scroll (2000×1500) | **0.5 ms**      | 0.8 ms         | 186.3 ms    | 3.2 ms        |\n| Very Large (4000×2000)   | **0.5 ms**      | 0.9 ms         | 425.9 ms    | 3.3 ms        |\n\n\n### Complex elements\n\n| Scenario                 | SnapDOM current | SnapDOM v1.9.9 | html2canvas | html-to-image |\n| ------------------------ | --------------- | -------------- | ----------- | ------------- |\n| Small (200×100)          | **1.6 ms**      | 3.3 ms         | 68.0 ms     | 14.3 ms       |\n| Modal (400×300)          | **2.9 ms**      | 6.8 ms         | 87.5 ms     | 34.8 ms       |\n| Page View (1200×800)     | **17.5 ms**     | 50.2 ms        | 178.0 ms    | 429.0 ms      |\n| Large Scroll (2000×1500) | **54.0 ms**     | 201.8 ms       | 735.2 ms    | 984.2 ms      |\n| Very Large (4000×2000)   | **171.4 ms**    | 453.7 ms       | 1,800.4 ms  | 2,611.9 ms    |\n\n\n### Run the benchmarks\n\n```sh\ngit clone https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom.git\ncd snapdom\nnpm install\nnpm run test:benchmark\n```\n\n\n## Roadmap\n\nPlanned improvements for future versions of SnapDOM:\n\n* [X] **Implement plugin system**\n  SnapDOM will support external plugins to extend or override internal behavior (e.g. custom node transformers, exporters, or filters).\n\n* [ ] **Refactor to modular architecture**\n  Internal logic will be split into smaller, focused modules to improve maintainability and code reuse.\n\n* [X] **Decouple internal logic from global options**\n  Functions will be redesigned to avoid relying directly on `options`. A centralized capture context will improve clarity, autonomy, and testability. See [`next` branch](https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Ftree\u002Fmain)\n\n* [X] **Expose cache control**\n  Users will be able to manually clear image and font caches or configure their own caching strategies.\n\n* [X] **Auto font preloading**\n  Required fonts will be automatically detected and preloaded before capture, reducing the need for manual `preCache()` calls.\n\n* [X] **Document plugin development**\n  A full guide will be provided for creating and registering custom SnapDOM plugins.\n\n* [ ] **Make export utilities tree-shakeable**\n  Export functions like `toPng`, `toJpg`, `toBlob`, etc. will be restructured into independent modules to support tree shaking and minimal builds.\n\nHave ideas or feature requests?\nFeel free to share suggestions or feedback in [GitHub Discussions](https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fdiscussions).\n\n\n## Development\n\n**Source layout:**\n- `src\u002Fapi\u002F` – Public API (`snapdom`, `preCache`)\n- `src\u002Fcore\u002F` – Capture pipeline, clone, prepare, plugins\n- `src\u002Fmodules\u002F` – Images, fonts, pseudo-elements, backgrounds, SVG\n- `src\u002Fexporters\u002F` – toPng, toSvg, toBlob, etc.\n- `dist\u002F` – Build output (`snapdom.js`, `snapdom.mjs`, `preCache.mjs`, `plugins.mjs`)\n\n**Build:**\n```sh\ngit clone https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom.git\ncd snapdom\ngit checkout dev\nnpm install\nnpm run compile\n```\n\n**Test:**\n```sh\nnpx playwright install   # Required for browser tests\nnpm test\nnpm run test:benchmark\n```\n\nFor detailed guidelines, see [CONTRIBUTING](https:\u002F\u002Fgithub.com\u002Fzumerlab\u002Fsnapdom\u002Fblob\u002Fmain\u002FCONTRIBUTING.md).\n\n\n## Contributors\n\n\u003C!-- CONTRIBUTORS:START -->\n\u003Cp>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftinchox5\" title=\"tinchox5\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F11557901?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"tinchox5\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FJarvis2018\" title=\"Jarvis2018\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F36788851?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"Jarvis2018\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Ftarwin\" title=\"tarwin\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F646149?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"tarwin\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FAmyuan23\" title=\"Amyuan23\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F25892910?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"Amyuan23\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fkohaiy\" title=\"kohaiy\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F15622127?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"kohaiy\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fairamhr9\" title=\"airamhr9\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F57371081?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"airamhr9\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FFlavioLimaMindera\" title=\"FlavioLimaMindera\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F96424442?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"FlavioLimaMindera\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fjswhisperer\" title=\"jswhisperer\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1177690?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"jswhisperer\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FK1ender\" title=\"K1ender\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F146767945?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"K1ender\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002F17biubiu\" title=\"17biubiu\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F13295895?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"17biubiu\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fav01d\" title=\"av01d\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6247646?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"av01d\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FCHOYSEN\" title=\"CHOYSEN\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F25995358?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"CHOYSEN\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fpedrocateexte\" title=\"pedrocateexte\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F207524750?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"pedrocateexte\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fclaude\" title=\"claude\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F81847?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"claude\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fdomialex\" title=\"domialex\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F4694217?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"domialex\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Felliots\" title=\"elliots\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F622455?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"elliots\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fstypr\" title=\"stypr\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6625978?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"stypr\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmon-jai\" title=\"mon-jai\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F91261297?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"mon-jai\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FRinZ27\" title=\"RinZ27\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F222222878?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"RinZ27\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fsharuzzaman\" title=\"sharuzzaman\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F7421941?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"sharuzzaman\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fsimon1uo\" title=\"simon1uo\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F60037549?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"simon1uo\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FtitoBouzout\" title=\"titoBouzout\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64156?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"titoBouzout\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FZiuChen\" title=\"ZiuChen\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F64892985?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"ZiuChen\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fharshasiddartha\" title=\"harshasiddartha\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F147021873?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"harshasiddartha\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FkarasHou\" title=\"karasHou\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F27048083?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"karasHou\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fjhbae200\" title=\"jhbae200\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F20170610?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"jhbae200\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fxiaobai-web715\" title=\"xiaobai-web715\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F81091224?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"xiaobai-web715\"\u002F>\u003C\u002Fa>\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fmiusuncle\" title=\"miusuncle\">\u003Cimg src=\"https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F7549857?v=4&s=100\" style=\"border-radius:10px; width:60px; height:60px; object-fit:cover; margin:5px;\" alt=\"miusuncle\"\u002F>\u003C\u002Fa>\n\u003C\u002Fp>\n\u003C!-- CONTRIBUTORS:END -->\n\n## Sponsors\n\nSpecial thanks to [@megaphonecolin](https:\u002F\u002Fgithub.com\u002Fmegaphonecolin), [@sdraper69](https:\u002F\u002Fgithub.com\u002Fsdraper69), [@reynaldichernando](https:\u002F\u002Fgithub.com\u002Freynaldichernando), [@gamma-app](https:\u002F\u002Fgithub.com\u002Fgamma-app) and [@jrjohnson](https:\u002F\u002Fgithub.com\u002Fjrjohnson),for supporting this project!\n\nIf you'd like to support this project too, you can [become a sponsor](https:\u002F\u002Fgithub.com\u002Fsponsors\u002Ftinchox5).\n\n## Star History\n\n[![Star History Chart](https:\u002F\u002Fapi.star-history.com\u002Fsvg?repos=zumerlab\u002Fsnapdom&type=Date)](https:\u002F\u002Fwww.star-history.com\u002F#zumerlab\u002Fsnapdom&Date)\n\n## License\n\nMIT © Zumerlab\n","SnapDOM 是一个高性能的DOM捕获引擎，能够快速地将DOM元素转换为多种格式。它支持将任何DOM子树转换为自包含的表示形式，并导出为SVG、PNG、JPG、WebP、Canvas或Blob等格式，同时支持通过插件扩展到自定义格式。核心功能包括完整的DOM捕获、嵌入样式和伪元素处理、以及对CSS计数器等功能的支持。由于其基于标准Web API构建且无外部依赖，因此运行速度极快。适用于需要网页截图、DOM元素导出或进行相关自动化处理的应用场景。",2,"2026-06-11 03:42:52","high_star"]