How to Save html2canvas Screenshots as PNG Images
Download html2canvas output as PNG, fix missing images and blank canvases, and choose between data URLs, Blobs, and ScreenshotNeo.
Direct answer: wait for html2canvas() to return a canvas, convert it with canvas.toDataURL('image/png'), then download that data URL from an anchor element.
html2canvas(document.querySelector('#capture')).then(canvas => {
const link = document.createElement('a');
link.download = 'screenshot.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
This creates a PNG in the browser. It does not capture the browser’s pixels literally: html2canvas reconstructs an image from the DOM and the CSS properties it supports. Read the official documentation before expecting pixel-perfect output.
Complete browser example
The following page loads html2canvas, captures one element, and downloads it when the button is clicked.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Save an html2canvas PNG</title>
<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<style>
#capture { width: 640px; padding: 32px; background: white; color: #111; }
</style>
</head>
<body>
<section id="capture">
<h1>Export this card</h1>
<p>The downloaded file will be named screenshot.png.</p>
</section>
<button id="save" type="button">Save PNG</button>
<script>
document.querySelector('#save').addEventListener('click', async () => {
const element = document.querySelector('#capture');
const canvas = await html2canvas(element, {
backgroundColor: '#ffffff',
scale: window.devicePixelRatio
});
const link = document.createElement('a');
link.download = 'screenshot.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
</script>
</body>
</html>
The same sequence with an async function is:
const canvas = await html2canvas(document.querySelector('#capture'));
const link = document.createElement('a');
link.download = 'screenshot.png';
link.href = canvas.toDataURL('image/png');
link.click();
Capture the right element and page size
One element
const element = document.querySelector('.invoice');
const canvas = await html2canvas(element);
const pngUrl = canvas.toDataURL('image/png');
Make sure the element is visible and has layout dimensions when the call runs. Wait for data, fonts, animations, and images that must appear in the export.
A long element or full page
For a scrollable element, pass its scroll dimensions so the rendering window covers the complete content:
const element = document.querySelector('#article');
const canvas = await html2canvas(element, {
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight
});
The official FAQ warns that very large canvases can be blank or truncated. Browser limits vary by engine, operating system, device memory, and updates. Its approximate guidance is 32,767 pixels maximum dimension and about 268 million pixels total area for Chrome/Chromium, about 472 million pixels for Firefox, and a similar area limit to Chrome for desktop Safari. iOS Safari can have lower limits.
Useful html2canvas options
| Option | Use it for |
|---|---|
scale |
Increase or reduce output resolution. window.devicePixelRatio is a common choice. |
backgroundColor |
Set a solid background, or use null for transparency. |
useCORS |
Attempt to load remote images with CORS. The image server must send suitable CORS headers. |
allowTaint |
Controls whether potentially tainting images are allowed. It is false by default; allowing taint prevents export. |
proxy |
Use a server-side proxy for remote assets that cannot be fetched with CORS. |
imageTimeout |
Limit how long html2canvas waits for images. |
windowWidth, windowHeight |
Control the virtual viewport, especially for full-page captures. |
scrollX, scrollY |
Set the virtual scroll position. |
ignoreElements |
Skip elements such as controls or private UI. |
onclone |
Modify the cloned document before rendering without changing the live page. |
logging |
Enable diagnostic logging while investigating missing resources. |
foreignObjectRendering |
Try the browser’s foreignObject path where supported; output and compatibility differ by browser. |
These and other settings are listed in the configuration reference.
PNG data URL versus a Blob download
toDataURL() is the shortest implementation and is the pattern shown in the official example. For larger images, a Blob avoids keeping the entire encoded data URL string in your application code:
const canvas = await html2canvas(document.querySelector('#capture'));
canvas.toBlob(blob => {
if (!blob) throw new Error('PNG encoding failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = 'screenshot.png';
link.href = url;
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
Both methods produce PNG data. Choose the data URL for a small, simple export and a Blob when you want an object URL or need to avoid handling a large base64 string. The research sources establish the workflows but do not provide a universal speed benchmark.
Missing images, fonts, and cross-origin content
Canvas origin rules still apply. A remote image can taint the canvas, after which reading it with toDataURL() raises a security error. With the default allowTaint: false, html2canvas skips images that would taint the canvas.
const canvas = await html2canvas(element, {
useCORS: true,
imageTimeout: 15000,
logging: true,
onclone: clonedDocument => {
clonedDocument.querySelectorAll('.cursor, .live-chat').forEach(node => node.remove());
}
});
useCORS works only when the image host returns an appropriate Access-Control-Allow-Origin header. Otherwise configure a proxy you control and pass its URL through proxy. Cross-origin iframes remain inaccessible because of browser security restrictions. Check the browser console and use the resource error callback described in the configuration reference.
Common problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Download is blank | The canvas exceeded browser dimensions or area limits. | Capture smaller sections, reduce scale, and set windowWidth/windowHeight deliberately. |
| Images are missing | Remote images lack CORS headers or would taint the canvas. | Enable useCORS with server CORS support, or use proxy. |
SecurityError from toDataURL() |
The canvas is tainted by cross-origin content. | Serve assets with CORS, proxy them, or remove those assets before capture. |
| Content is cut off | The virtual rendering window is smaller than the element. | Use the element’s scrollWidth and scrollHeight. |
| Styles differ from the page | html2canvas manually implements CSS and does not support every property. | Simplify unsupported effects, add an export-only style in onclone, or use a real browser screenshot service. |
| Web fonts are not reflected | Capture ran before fonts finished loading. | Await document.fonts.ready before calling html2canvas. |
| Animations produce inconsistent frames | The DOM changed during rendering. | Pause animations in an export class or in onclone. |
await document.fonts.ready;
document.querySelectorAll('img').forEach(img => {
if (!img.complete) img.decode?.().catch(() => {});
});
const canvas = await html2canvas(element);
Performance, reliability, and cost
- Large width, height, and
scalevalues multiply memory use. Capture sections separately when a full page approaches canvas limits. - Wait for network content explicitly; otherwise a valid PNG may contain placeholders or missing images.
- Use a fixed viewport, background, font state, and animation state when generating repeatable files.
- html2canvas runs in the user’s browser, so output depends on that browser, its available memory, and origin permissions. It has no hosted rendering charge, but your page pays the CPU and memory cost.
- For a literal browser render, cross-origin pages, or server-side automation, use a browser screenshot API instead of reconstructing the DOM.
Or skip the browser setup
ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. See the API documentation for all options.
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}`);
const buffer = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', buffer);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, 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 use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account.
FAQ
Does html2canvas save a real screenshot?
No. It builds a canvas from DOM information and supported styles, so the PNG can differ from the browser’s visible pixels.
Can I change the filename?
Yes. Set any name ending in .png on link.download.
Why is my PNG transparent?
Set backgroundColor: '#fff' or another color. A null background preserves transparency where the browser can render it.
Can html2canvas capture a cross-origin iframe?
Not directly. Browser security prevents access to the iframe’s document; capture content from the same origin or use a server-side browser service.
When should I use a screenshot API?
Use one when you need a remote URL rendered consistently, need to avoid browser CORS setup, or require server-side jobs and PDFs.


