How to Export an HTML Element to PNG
Select a DOM element, render it with html2canvas or html-to-image, encode it with toBlob(), and download a PNG safely.
Short answer: select the element, render it with a DOM-to-image library, encode the resulting canvas as a PNG Blob with HTMLCanvasElement.toBlob(), then download it through an object URL. The most common choices are html2canvas and html-to-image. Both reconstruct the element from DOM and style information, so test the exact component, fonts, images, and browsers you support.
1. The complete browser workflow
- Find the element with
document.querySelector(). - Wait until its content, fonts, and images are ready.
- Render the node with
html2canvas()ortoPng(). - Encode the result as PNG with
canvas.toBlob()or receive a PNG data URL/blob from the library. - Create an object URL, click a temporary download link, and revoke the URL after the download starts.
Runnable html2canvas example
Install html2canvas with your package manager, then include this module in the page containing the element:
import html2canvas from 'html2canvas';
async function downloadElementAsPng(selector, filename = 'capture.png') {
const element = document.querySelector(selector);
if (!element) throw new Error(`Element not found: ${selector}`);
const canvas = await html2canvas(element, {
backgroundColor: null,
useCORS: true,
scale: window.devicePixelRatio || 1
});
const blob = await new Promise((resolve) => {
canvas.toBlob(resolve, 'image/png');
});
if (!blob) throw new Error('PNG encoding failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
// Give the browser time to begin the download before releasing the URL.
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
document.querySelector('#download').addEventListener('click', () => {
downloadElementAsPng('#capture', 'dashboard-card.png').catch(console.error);
});
toBlob() produces a PNG when the requested type is image/png; PNG is also the default when the type is omitted or unsupported. A Blob and object URL avoid the large in-memory string created by toDataURL(). See MDN’s toBlob() documentation.
html-to-image alternative
html-to-image exposes toPng(), toBlob(), and other output functions. Its documented process clones the DOM, copies styles, embeds fonts and images where possible, wraps the result in SVG foreignObject, and rasterizes it.
import { toBlob } from 'html-to-image';
async function downloadWithHtmlToImage(selector, filename = 'capture.png') {
const element = document.querySelector(selector);
if (!element) throw new Error(`Element not found: ${selector}`);
const blob = await toBlob(element, {
cacheBust: true,
pixelRatio: window.devicePixelRatio || 1,
backgroundColor: '#ffffff'
});
if (!blob) throw new Error('PNG encoding failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}
downloadWithHtmlToImage('#capture').catch(console.error);
2. Preparing the element for a reliable export
Make the element measurable
Render after the component is mounted and visible. A node with display: none, zero dimensions, collapsed parents, or content that has not finished loading can produce an empty or incomplete image.
await document.fonts.ready;
await Promise.all(
[...document.images].map((image) => image.complete
? Promise.resolve()
: new Promise((resolve) => {
image.addEventListener('load', resolve, { once: true });
image.addEventListener('error', resolve, { once: true });
}))
);
Choose the output dimensions
The output size follows the element’s layout size multiplied by the renderer’s scale or pixel-ratio setting. A higher ratio gives sharper output but consumes more memory and produces larger files. Set an explicit width and height on responsive cards when consistent exports matter.
Control the background
Use backgroundColor: null for transparency when the library supports it, or provide a solid color for predictable PNGs. Transparent output can look different when opened against a dark viewer.
Hide controls and transient UI
Clone-time hooks or temporary CSS can hide buttons, focus rings, hover menus, and animations. Pause animations and remove blinking cursors before capture. With html2canvas, onclone lets you adjust the cloned document without changing the live page:
const canvas = await html2canvas(document.querySelector('#capture'), {
onclone: (clonedDocument) => {
clonedDocument.querySelectorAll('[data-export-hide]').forEach((node) => {
node.style.display = 'none';
});
}
});
3. Cross-origin images, fonts, and iframes
Canvas security is the most common reason an apparently correct export fails. An image loaded from another origin must be served with permission for your origin through CORS. Set the image’s crossorigin attribute before assigning its source, and configure the image server to return an appropriate Access-Control-Allow-Origin header. A client-side option cannot override browser policy; see MDN’s CORS image guide.
<img crossorigin="anonymous" src="https://cdn.example.com/chart.png" alt="">
If a cross-origin image is not origin-clean, reading the canvas or calling toBlob() can throw a SecurityError. Proxy the asset through your own origin, enable CORS on the asset host, or replace it with a same-origin copy.
html2canvas cannot inspect the contents of a cross-origin iframe. You need cooperation from the framed application, a same-origin deployment, or a server-side/browser capture that can load the complete page.
Web fonts must be loaded before rendering. If a font is missing, the export may use a fallback even though the live page looked correct. Wait for document.fonts.ready and ensure the font files are accessible from the page origin.
4. Library choices and native capture
| Option | Use it when | Important limits |
|---|---|---|
| html2canvas | You want a familiar node-to-canvas API. | It reconstructs the view from DOM and styles; unsupported CSS and cross-origin content can differ. |
| html-to-image | You need PNG, Blob, SVG, or canvas outputs from a cloned DOM. | It relies on SVG foreignObject, embedded assets, browser support, and available memory. |
| Element Capture | You need a native capture path in browsers that support it. | Requires Element Capture, screen-capture flow, and ImageCapture.grabFrame(); it is not universal. |
Compare the actual CSS used by your component, image and font origins, iframe requirements, browser coverage, element size, output format, and project maintenance. No library guarantees pixel-perfect results for every DOM tree.
5. PNG encoding and download details
Prefer a Blob for downloads. toDataURL() turns the complete image into an in-memory string and can increase memory pressure, especially for large elements. Browser canvas dimensions are also limited and vary by browser and environment; see MDN’s canvas reference.
function canvasToPngBlob(canvas) {
return new Promise((resolve, reject) => {
try {
canvas.toBlob((blob) => {
if (blob) resolve(blob);
else reject(new Error('The browser returned no PNG Blob'));
}, 'image/png');
} catch (error) {
reject(error);
}
});
}
For a very large dashboard, capture smaller sections, reduce the pixel ratio, or export a server-side PDF/image instead of creating one enormous canvas.
6. Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| “Element not found” | The selector is wrong or the component has not mounted. | Run after rendering, verify the selector, and fail visibly. |
| Blank or cropped image | Hidden/zero-size node, overflow, unfinished async content, or canvas limits. | Give the node dimensions, wait for fonts/images, capture a smaller region, and inspect computed styles. |
| Missing remote images | The image host did not grant CORS permission. | Enable CORS, use same-origin assets, or proxy them. |
SecurityError from canvas |
The bitmap is tainted by cross-origin content. | Fix CORS or remove the offending asset; JavaScript cannot bypass the policy. |
| Cross-origin iframe is empty | The renderer cannot read another origin’s document. | Use same-origin content or capture the complete page in a browser/server environment. |
| Text uses the wrong font | Fonts were not loaded or could not be fetched. | Await document.fonts.ready and check font CORS and network errors. |
| Styles or filters differ | The library does not implement every CSS property. | Test the target CSS, simplify unsupported effects, or use a real browser screenshot service. |
| Download does nothing | Popup/download policy, revoked URL too early, or no Blob. | Start from a user click, delay revocation, and check the Blob for null. |
| Browser freezes | Large DOM, high pixel ratio, huge data URLs, or memory pressure. | Lower scale, split captures, remove unnecessary nodes, and use Blob output. |
7. Performance, reliability, and cost
- Performance: the work runs on the user’s main thread and grows with DOM complexity, image dimensions, fonts, and pixel ratio. Capture only the required element.
- Reliability: wait for stable layout and loaded assets; disable animations; test Chromium, Firefox, and Safari versions relevant to your users.
- Privacy: browser-side rendering keeps the page in the user’s browser, while a hosted renderer receives the URL and any data required to load it. Choose based on your data boundary.
- Cost: open-source browser libraries have no per-capture API charge, but they consume client CPU and engineering time. Hosted capture is useful when you need repeatable server-side rendering, remote pages, or a simple HTTP workflow.
8. Or skip the browser setup
For a remote page or a production workflow, ScreenshotNeo provides an HTTP screenshot API and MCP server. Its element capture option accepts a CSS selector, so you can request one card or panel without shipping browser automation code. See the ScreenshotNeo API documentation for all parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -d selector="#capture" -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "selector": "#capture"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com', selector: '#capture' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. An MCP server lets Claude, Cursor, and other MCP clients take screenshots. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and start with 1,000 screenshots per month at no cost.
9. FAQ
Can I export an element without a library?
Native Element Capture can work in supporting browsers, but it requires a screen-capture flow and specific API support. It is not a universal replacement for html2canvas or html-to-image.
Why is my PNG not identical to the page?
DOM-to-image libraries reconstruct pixels from DOM and styles rather than taking a literal screenshot. Unsupported CSS, fonts, filters, pseudo-elements, and cross-origin assets can change the result.
Should I use PNG or JPEG?
PNG preserves sharp text, transparency, and flat UI graphics. JPEG can be smaller for photographic content but does not preserve transparency. This guide focuses on PNG.
Can I export a cross-origin iframe?
Not by reading its document from the parent page. Same-origin policy prevents html2canvas from inspecting a cross-origin iframe; use cooperation from the framed page or a complete-page browser capture.
How do I export a full page?
Increase the capture region or use a full-page browser screenshot tool. Very tall canvases can exceed browser limits, so split the page or use a hosted capture workflow.


