How to Take Screenshots with html2canvas
Learn how html2canvas rebuilds a DOM element into a canvas, handle CORS and full-page limits, and export reliable PNG screenshots.

Direct answer: install @html2canvas/html2canvas, select an element, and await html2canvas(element, options). The Promise returns a <canvas> that you can display or export with toDataURL() or toBlob().
<div id="capture">
<h1>Invoice preview</h1>
<p>Rendered in the browser.</p>
</div>
<button id="save">Save PNG</button>
<script type="module">
import html2canvas from 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/+esm';
document.querySelector('#save').addEventListener('click', async () => {
const element = document.querySelector('#capture');
const canvas = await html2canvas(element);
const link = document.createElement('a');
link.download = 'invoice-preview.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
</script>
That is the complete browser workflow. html2canvas does not take a literal monitor screenshot. It traverses the DOM and redraws supported HTML and CSS into a canvas, so the result can differ from what the browser compositor displays. The project documentation describes it as taking “screenshots” directly in the user’s browser. Read the official documentation and options reference for the current API.
1. Install and capture an element
Package installation
npm install @html2canvas/html2canvas
# or
pnpm add @html2canvas/html2canvas
# or
yarn add @html2canvas/html2canvas
import html2canvas from '@html2canvas/html2canvas';
const element = document.querySelector('#capture');
if (!element) throw new Error('Missing #capture element');
const canvas = await html2canvas(element);
document.body.appendChild(canvas);
The library runs in a browser because it needs DOM, CSSOM, image and canvas APIs. It is not a Node.js screenshot engine. For server-side work, the html2canvas FAQ points to browser automation tools such as Puppeteer or Playwright.

Make the download explicit
async function downloadScreenshot(selector, filename = 'screenshot.png') {
const element = document.querySelector(selector);
if (!element) throw new Error(`No element matches ${selector}`);
const canvas = await html2canvas(element, {
backgroundColor: '#ffffff',
scale: window.devicePixelRatio
});
canvas.toBlob((blob) => {
if (!blob) throw new Error('Canvas export failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = filename;
link.href = url;
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
}
downloadScreenshot('#capture');
toBlob() avoids placing a very large base64 string in memory. Use toDataURL() when you need an inline data URL, for example in a JSON response or an img element.
2. Choose the output size, scale and crop
The default scale is the browser’s window.devicePixelRatio. A retina display can therefore produce a canvas twice as wide and twice as tall as the CSS box. Higher scale improves detail but increases memory use and the chance of hitting canvas limits.
const canvas = await html2canvas(document.querySelector('#capture'), {
scale: 2,
width: 800,
height: 450,
x: 0,
y: 0,
backgroundColor: '#f7f7f8'
});
| Option | Use | Practical note |
|---|---|---|
scale |
Output pixel density | Lower it for large captures; raise it for crisp previews. |
width, height |
Canvas dimensions | Useful for a fixed crop or consistent thumbnails. |
x, y |
Crop origin in the source | Coordinates are relative to the element being rendered. |
scrollX, scrollY |
Simulated scroll position | Helps reproduce fixed-position elements at a chosen offset. |
windowWidth, windowHeight |
Viewport used for media queries | Set these when responsive CSS changes the layout you need. |
Capture a full element
For a long component, use its scroll dimensions as the rendering viewport. This can prevent content from being cut off, but it cannot remove browser canvas limits.
const element = document.querySelector('#long-report');
const canvas = await html2canvas(element, {
width: element.scrollWidth,
height: element.scrollHeight,
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
scale: 1
});
Very large canvases may be blank or partially rendered without an exception. Approximate limits reported by the project FAQ include dimensions around 32,767 pixels in several desktop browsers, with total-area limits varying by engine; iOS Safari is more dependent on device memory. Treat those figures as observations, not guarantees. For long pages, capture sections separately or use a browser screenshot service.
3. Control backgrounds, ignored UI and cloned markup
Transparent output
const canvas = await html2canvas(element, {
backgroundColor: null
});
A transparent background works when the rendered content itself does not paint an opaque background.
Exclude controls and overlays
Add data-html2canvas-ignore to markup that should never appear:
<button data-html2canvas-ignore>Delete</button>
<div class="debug-panel" data-html2canvas-ignore>Debug only</div>
For dynamic rules, use ignoreElements:
const canvas = await html2canvas(element, {
ignoreElements: (node) => node.matches('.cookie-banner, .chat-widget, [aria-busy="true"]')
});
Modify the cloned document
onclone runs after html2canvas clones the document but before it renders. This is useful for adding a print class, expanding a collapsed panel, or hiding animation.
const canvas = await html2canvas(element, {
onclone: (clonedDocument) => {
clonedDocument.documentElement.classList.add('screenshot-mode');
clonedDocument.querySelectorAll('*').forEach((node) => {
node.style.animation = 'none';
node.style.transition = 'none';
});
}
});
4. Handle images, fonts and cross-origin content
Browser security still applies. An image from another origin must opt in with suitable CORS headers if you want it drawn and exported safely. useCORS: true asks the browser to make a CORS request; it cannot bypass a server that omits Access-Control-Allow-Origin.
const canvas = await html2canvas(element, {
useCORS: true,
allowTaint: false,
imageTimeout: 15000
});
If you control the image server, configure it to return an appropriate Access-Control-Allow-Origin value and ensure the image request is cacheable. If you do not control it, the documented alternative is a same-origin proxy:
const canvas = await html2canvas(element, {
proxy: '/image-proxy',
useCORS: true
});
Your proxy must fetch only permitted resources, return the correct content type, and add the CORS headers your page needs. Do not treat it as a way to defeat access controls. Images that cannot be loaded may be skipped, and a canvas containing disallowed cross-origin pixels can become tainted, preventing export.
Wait for fonts and images before capture:
await document.fonts.ready;
await Promise.all([...document.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 });
});
}));
const canvas = await html2canvas(document.querySelector('#capture'));
5. CSS fidelity and browser behavior
Because html2canvas reconstructs the page, only CSS properties it understands are represented. Complex filters, blend modes, video frames, plugins, cross-origin iframes and some pseudo-element or compositing behavior may differ from a normal browser screenshot. Test the exact browsers you support.
Responsive layouts use the cloned viewport dimensions. To capture the desktop branch of a media query from a narrow window, provide an explicit windowWidth:
const canvas = await html2canvas(element, {
windowWidth: 1440,
windowHeight: 900,
scale: 1
});
Freeze volatile content before capture: stop carousels, wait for data, disable blinking cursors, and capture after the final layout pass. A delayed capture can be combined with your own wait:
await new Promise((resolve) => setTimeout(resolve, 500));
const canvas = await html2canvas(element);
6. Troubleshooting checklist
| Symptom | Cause | Fix |
|---|---|---|
| Images are missing | Remote image lacks CORS permission or failed to load | Use useCORS with server headers, or a same-origin proxy. Check the image URL in DevTools. |
| Export throws a security error | The canvas is tainted by cross-origin pixels | Serve the asset with CORS, proxy it, or omit it. allowTaint does not make export safe. |
| Canvas is blank or cut off | Output exceeds platform limits | Reduce scale, split the page, and set viewport dimensions to the element’s scroll size. |
| CSS looks different | Unsupported or composited CSS | Simplify the style for capture, add a screenshot class in onclone, or use a native browser screenshot. |
| Fonts change layout | Capture ran before web fonts finished | Await document.fonts.ready and confirm font requests succeeded. |
| Fixed header appears in the wrong place | Scroll offsets differ in the clone | Set scrollX/scrollY, or temporarily convert fixed elements to absolute positioning. |
| Nothing works in Node.js | html2canvas needs browser APIs | Run it in a page, or use Puppeteer/Playwright for server-side capture. |
| Browser extension capture is unreliable | Extension context and canvas limits | The FAQ recommends the browser’s native extension screenshot API. |
7. cURL, Python and Node.js alternatives
html2canvas is client-side JavaScript. If your application needs a URL-to-image endpoint, these examples show a hosted browser capture call. With ScreenshotNeo, one GET request returns PNG, JPEG, WebP or PDF. See the ScreenshotNeo API documentation for options.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
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());
require('node:fs').writeFileSync('shot.webp', image);
8. Or skip the browser setup
ScreenshotNeo handles the browser capture step through its API and MCP server. Cookie and consent banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether the request was billed. An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.

You can also select an element, capture full pages with lazy images loaded, set a device or viewport, use retina scale, apply custom CSS or JavaScript, click before capture, hide selectors, wait for a selector, delay or network idle, block ads or resource types, provide headers, cookies, a user agent, Authorization, timezone or geolocation, create PDFs, resize images, cache with a chosen TTL, generate signed image links, submit async jobs with signed webhooks, capture up to 100 URLs per call, and read usage through the API. Existing parameter names used by other screenshot APIs also work, which can simplify migration.
The Free plan includes 1,000 shots per 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.
9. Performance, reliability and cost considerations
- Reduce pixels first: use the smallest practical crop and scale. Pixel count grows with width multiplied by height and scale squared.
- Reuse stable assets: wait for fonts and images once, then capture related components in sequence.
- Split long reports: section captures are more reliable than one canvas near platform limits.
- Keep the main thread responsive: large DOM trees can block interaction while they are cloned and painted; schedule captures after user actions or in a worker-friendly workflow where possible.
- Choose the right environment: use html2canvas for an in-page preview or user-triggered export; use native extension APIs for extension screenshots; use headless browser automation or ScreenshotNeo for server jobs.
- Measure failures: log target dimensions, scale, browser, missing assets and export errors. A successful Promise does not guarantee that an oversized canvas contains every pixel.
10. FAQ
Does html2canvas capture the whole monitor?
No. It renders a selected DOM element or region into a canvas. Browser chrome, other tabs and pixels outside the page are not included.
Can it capture an iframe?
Same-origin iframe content may be accessible, but cross-origin frame security restrictions still apply. You cannot use html2canvas to bypass origin policy.
Which format should I export?
PNG is lossless and supports transparency. JPEG is smaller for photographic content but has no transparency. WebP can reduce size when your consumers support it.
Why is my screenshot different on mobile?
Media queries, device pixel ratio, fonts and viewport dimensions change layout. Set windowWidth and windowHeight deliberately and test on the target browser.
Should I use html2canvas for a server-rendered social card?
Usually not. It requires a browser page and client resources. Use a headless browser or a screenshot API when the job must run reliably on a server without a user’s tab.
Is there a native browser screenshot API?
Yes. The html2canvas FAQ recommends native extension screenshot APIs for browser extensions because they avoid the library’s DOM reconstruction and canvas-size constraints.


