ScreenshotNeo

BlogHow-to

How to Screenshot a Single Element with dom-to-image

Capture one DOM element as PNG, JPEG, SVG, Blob, canvas, or pixels with dom-to-image, including filters, fonts, cross-origin fixes, and troubleshooting.

By the ScreenshotNeo team29 September 20269 min read

How to Screenshot a Single Element with dom-to-image

To screenshot one element with dom-to-image, resolve the element to a DOM node and pass that node to domtoimage.toPng(node). The promise resolves to a PNG data URL that you can display, download, upload, or convert to a Blob. The same node can be rendered as JPEG, SVG, Blob, canvas, or raw pixel data.

import domtoimage from 'dom-to-image';

const node = document.getElementById('my-element');

if (!node) {
  throw new Error('Element not found');
}

domtoimage.toPng(node)
  .then((dataUrl) => {
    const image = new Image();
    image.src = dataUrl;
    document.body.appendChild(image);
  })
  .catch((error) => {
    console.error('Could not render element', error);
  });

This is a DOM renderer, not an operating-system screenshot. It clones the selected subtree, copies computed styles, recreates pseudo-elements, embeds fonts and images, serializes the result through SVG foreignObject, and uses an image and canvas path for raster output. That design makes the target element easy to isolate, but external resources, canvas security, browser behavior, and content size affect the result. See the original dom-to-image documentation for the package API and implementation notes.

1. Install dom-to-image and create a target element

Install the package in a browser-based JavaScript project:

dom-to-image clones the selected DOM subtree and renders it through an SVG and canvas path.
dom-to-image clones the selected DOM subtree and renders it through an SVG and canvas path.
npm install dom-to-image

Give the element a stable ID or class. The element must exist in the browser DOM when you call the renderer.

<article id="invoice-card" class="invoice-card">
  <h1>Invoice #1042</h1>
  <p>Paid · March 2026</p>
  <strong>$249.00</strong>
</article>

<button id="save-invoice">Save image</button>

Then import the package and resolve the node:

import domtoimage from 'dom-to-image';

const card = document.querySelector('.invoice-card');
if (!card) {
  throw new Error('The invoice card is not in the DOM');
}

const dataUrl = await domtoimage.toPng(card);
console.log(dataUrl.slice(0, 40));

Pass the actual node, not a selector string. document.querySelector, getElementById, or a framework ref can supply the node. The null check is ordinary defensive DOM code; it is not a special dom-to-image requirement.

2. Display, download, or upload the result

Display the image

const preview = document.createElement('img');
preview.alt = 'Rendered invoice';
preview.src = await domtoimage.toPng(card);
document.body.appendChild(preview);

Download a PNG

const dataUrl = await domtoimage.toPng(card);
const link = document.createElement('a');
link.download = 'invoice-1042.png';
link.href = dataUrl;
link.click();

Use a Blob for uploads

const blob = await domtoimage.toBlob(card);
const form = new FormData();
form.append('file', blob, 'invoice-1042.png');
await fetch('/uploads', { method: 'POST', body: form });

A data URL is convenient for a preview or small download. A Blob is generally a better boundary for network uploads because it avoids keeping a long base64 string in application state.

3. Choose the output format

All top-level functions accept a DOM node and rendering options and return promises. Pick the output for the next operation:

Method Result Use it when
toPng PNG data URL You need lossless raster output or a quick browser preview.
toJpeg JPEG data URL You want a smaller compressed image and can accept lossy encoding.
toSvg SVG data URL You want the serialized SVG container.
toBlob Blob You will upload, store, or download the result.
toCanvas HTML canvas You need to draw, resize, or process it with browser canvas APIs.
toPixelData Raw RGBA pixel data You need image analysis or custom pixel processing.
const jpegUrl = await domtoimage.toJpeg(card, { quality: 0.85 });
const svgUrl = await domtoimage.toSvg(card);
const canvas = await domtoimage.toCanvas(card);
const pixels = await domtoimage.toPixelData(card);

The quality option applies to JPEG output and ranges from 0 to 1. PNG does not use JPEG quality settings.

4. Control what gets rendered

Exclude descendants with filter

Use filter(node) to omit descendants. Return true to keep a node and false to exclude it. Excluding a parent excludes its entire subtree. The filter is not called for the root capture node, so it cannot remove the root itself.

const options = {
  filter(node) {
    return node.tagName !== 'BUTTON';
  }
};

const png = await domtoimage.toPng(card, options);

This is useful for removing print controls, menus, live status badges, or other descendants that should not appear in the exported image.

Set a background color

const png = await domtoimage.toPng(card, {
  bgcolor: '#ffffff'
});

A background is helpful when the source element is transparent but the destination expects an opaque image.

Override dimensions

const png = await domtoimage.toPng(card, {
  width: 1200,
  height: 800
});

Width and height change the rendered node dimensions. They do not automatically reproduce a responsive layout at every breakpoint, so inspect the output at the dimensions you intend to publish.

Apply temporary styles

const png = await domtoimage.toPng(card, {
  style: {
    padding: '32px',
    borderRadius: '0',
    boxShadow: 'none'
  }
});

Style overrides are applied to the cloned root for the render. Keep the source page unchanged and put export-only presentation rules here.

Handle cache and failed images

const png = await domtoimage.toPng(card, {
  cacheBust: true,
  imagePlaceholder: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
});

cacheBust appends the current time to resource URLs. imagePlaceholder supplies a data URL when an image fetch fails. Without a placeholder, an image failure can reject the render.

5. Wait for fonts, images, and layout before capture

Call dom-to-image only after the element has its final layout. A practical sequence is:

  1. Render the component and make it visible.
  2. Wait for document fonts when the browser exposes document.fonts.ready.
  3. Wait for images inside the target to finish loading.
  4. Capture on the next animation frame so layout and paint have settled.
async function waitForImages(root) {
  const images = [...root.querySelectorAll('img')];
  await Promise.all(images.map((img) => {
    if (img.complete) return Promise.resolve();
    return new Promise((resolve) => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
}

async function captureAfterLoad(root) {
  if (document.fonts?.ready) {
    await document.fonts.ready;
  }
  await waitForImages(root);
  await new Promise(requestAnimationFrame);
  return domtoimage.toPng(root);
}

const png = await captureAfterLoad(card);

This does not bypass cross-origin restrictions. It only prevents an avoidable race where the clone is made before a font or image has loaded.

6. Resource, browser, and content limitations

Because the library clones and fetches resources, remote images, CSS background images, and web fonts can change the result. A canvas that contains cross-origin content can become tainted, preventing later pixel reads or causing a render failure. Make assets same-origin where possible, configure appropriate CORS response headers, or replace problematic assets with data URLs before capture.

The original README contains historical warnings about Internet Explorer lacking SVG foreignObject and Safari enforcing stricter security around it. Those statements describe the project’s documentation at the time and are not a current browser-support test. Validate the exact browsers and content your application promises.

The separate dom-to-image-more fork documents additional caveats: rendering requires a browser DOM rather than server-only execution, cross-origin iframe content cannot be accessed, browsers limit canvas dimensions, and video needs a poster or a caller-created image or canvas representation. Treat those as fork-specific documentation when deciding whether they apply to your package.

7. Common errors and fixes

Error or symptom Likely cause Fix
Cannot read properties of null The selector ran before the element existed or matched nothing. Run after rendering, use a stable selector, and check the node before calling the API.
Blank or missing images An image was still loading, failed to fetch, or was blocked by origin policy. Wait for images, use cacheBust, fix CORS, or provide imagePlaceholder.
Custom font is missing The font had not loaded or could not be fetched by the cloned document. Await document.fonts.ready and verify the font response is accessible.
Render rejects with a security or tainted-canvas error Cross-origin image, background, font, iframe, or canvas content. Use same-origin assets, correct CORS headers, or remove/replace the resource.
Buttons or controls appear in the export They are descendants of the target and were cloned normally. Exclude them with filter or hide them with an export style.
Text is clipped The target has fixed dimensions, overflow rules, or a render size different from the source. Inspect computed dimensions, set explicit width/height, and adjust overflow.
Very large capture fails Canvas or browser bitmap dimensions are limited. Capture smaller sections, reduce dimensions, or use a server-side capture service.
Video is blank Video frames are not represented as ordinary static image content. Use a poster frame or draw a frame to a canvas before capture.

8. Performance and reliability guidance

  • Capture only the required subtree. Cloning a small card is faster and uses less memory than cloning the entire page.
  • Reuse a prepared export component when you need many images instead of repeatedly changing a complex live layout.
  • Wait for fonts and images once, then capture multiple formats from the settled node.
  • Prefer toBlob for uploads and avoid storing large base64 strings in application state.
  • Set explicit dimensions for repeatable output, especially when fonts, responsive CSS, or animation are involved.
  • Disable animations and blinking cursors in export styles so two captures do not differ.
  • Test the largest expected element. Browser canvas limits are practical constraints, not just performance concerns.

dom-to-image runs in the browser and consumes the user’s CPU and memory. For unattended batches, pages you do not control, or content that requires cookie handling and browser automation, a screenshot API can remove that setup.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF, with options for full-page capture, a CSS element selector, dark mode, device presets, retina scale, custom CSS and JavaScript, click and wait actions, request blocking, headers, cookies, user agents, timezone, geolocation, caching, signed links, asynchronous jobs, bulk capture, and usage reporting. The API also accepts parameter names used by other screenshot services, which simplifies migration. See the ScreenshotNeo API documentation for the complete option list.

A capture service can remove consent banners, popups, and chat widgets before rendering.
A capture service can remove consent banners, popups, and chat widgets before rendering.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

For a single element, supply the API’s element selector option alongside the target URL. ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account and start with the included 1,000 screenshots.

10. FAQ

Does dom-to-image capture the whole page?

It captures the node you pass. Pass a page container for a larger region, but the library does not automatically provide a browser-style full-page scroll capture.

Can I pass a CSS selector directly?

No. Resolve the selector first with querySelector or another DOM method, then pass the resulting element node.

Which format should I use for transparent graphics?

Use PNG or SVG and avoid setting an opaque bgcolor. JPEG does not preserve transparency.

Why is the output different from the visible page?

The renderer clones computed styles and resources. Fonts, external assets, animations, cross-origin content, and dimensions can therefore produce differences. Capture after the layout is settled and make export dimensions explicit.

Is dom-to-image suitable for server-side rendering?

The original package requires a browser DOM. For server-side or scheduled captures, use a browser service such as ScreenshotNeo or run a real browser environment that loads the page.