smooth-screenshot
v1.1.4
Published
Capture any DOM element to PNG, JPEG, WebP, SVG or PDF without ever freezing the UI. Cancellable, progress-reporting, handles huge content via tiling. Zero dependencies.
Maintainers
Readme
smooth-screenshot
Live demo & docs → luccas-carvalho.github.io/smooth-screenshot
Capture any DOM element to PNG, JPEG, WebP, SVG, or PDF — without ever freezing the UI.
Most DOM-to-image libraries do all their work in one synchronous burst: on a large
element the page locks up, animations stutter, and your loading spinner freezes
mid-spin. smooth-screenshot slices the work across frames with an internal
cooperative scheduler, so the main thread stays responsive the whole time. Your
skeletons and progress animations keep running while the capture happens.
- Never blocks the main thread — work yields to the event loop on a frame budget.
- Per-phase progress — drive real loading states (
clone→embed→rasterize→encode). - Cancellable — pass an
AbortSignal. - Huge content — PNG and PDF render in memory-bounded tiles, beyond the browser's single-canvas size limit.
- Multi-format —
toPng,toJpeg,toWebp,toSvg,toPdf,toBlob,toCanvas. - Zero runtime dependencies. Inline web worker, hand-written PDF, native compression.
npm install smooth-screenshotUsage
import { toPng, toPdf, toSvg } from 'smooth-screenshot'
const node = document.querySelector('#capture')!
// PNG data URL
const url = await toPng(node)
// PDF blob
const pdf = await toPdf(node)
// SVG (vector, infinite zoom)
const svg = await toSvg(node)Loading state that doesn't freeze
await toPng(node, {
onProgress({ phase, overall }) {
setLabel(phase) // 'clone' | 'embed' | 'rasterize' | 'encode'
setBar(overall) // 0..1
},
})Cancellation
const controller = new AbortController()
cancelButton.onclick = () => controller.abort()
try {
await toPng(node, { signal: controller.signal })
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
// user cancelled
}
}Options
| Option | Default | Description |
| --- | --- | --- |
| type | 'png' | Image format for toBlob (png | jpeg | webp). |
| quality | 0.92 | 0–1 for lossy formats. |
| scale | devicePixelRatio | Output pixel density (alias: pixelRatio). |
| backgroundColor | transparent | Background painted behind the element (JPEG defaults to white). |
| width / height | element size | Force output dimensions (CSS px). |
| crop | — | { x, y, width, height } — capture a sub-region. Use for full-res sections of very large content. |
| style | — | Inline styles (CSS property names) applied to the clone root — reframe/restyle output without touching the live DOM. |
| signal | — | AbortSignal to cancel the capture. |
| onProgress | — | (p: { phase, phaseRatio, overall }) => void. |
| frameBudgetMs | 5 | Main-thread time per slice before yielding. |
| worker | true | Offload resource fetching to an inline worker. false or { count }. |
| maxTileSize | 4096 | Tile edge (device px) before tiling kicks in. |
| filter | — | (node) => boolean to exclude nodes. |
| fonts | — | { skip?, preferWoff2? }. |
| fetch | — | { requestInit?, placeholder?, timeoutMs?, retries? }. |
| onResourceError | — | (url, error) => void — a failed resource doesn't fail the capture. |
Notes & limits
- Very large content:
toPngandtoPdfrender at full resolution via tiling.toJpeg/toWebp/toCanvasare bound by the browser's single-canvas size limit and are downscaled to fit when exceeded. To export huge content crisply in those formats, capture sections withcrop. - Cross-origin resources must allow CORS, otherwise they can't be embedded and
are replaced with a transparent placeholder (see
onResourceError). - Browser support: requires
CompressionStreamfor tiled PNG/PDF output (Chrome 80+, Firefox 113+, Safari 16.4+).
License
MIT © Luccas Carvalho
