Capture dynamic content only after your application has finished rendering it. Select the element that contains the final UI, call html2canvas(), await its Promise, and then export the returned canvas. Use the clone hook to remove controls or adjust styles for the capture. Remember that html2canvas reconstructs an image from the DOM and styles it can read; it is not a pixel-for-pixel screenshot of the browser.
The reliable capture sequence
A dynamic page is usually rendered in stages: an initial shell, asynchronous data, images, fonts, and sometimes a second layout pass. Calling html2canvas during any earlier stage produces a valid canvas of the wrong state. The dependable sequence is:
- Wait for your own data request and rendering code to complete.
- Select the specific component or region to capture.
- Call
html2canvas(element, options)and await the returned Promise. - Export the canvas as a Blob, data URL, or download.
There is no universal “page is ready” event for an application. Connect capture to the promise, state transition, or custom event that your app already uses. A fixed timeout can be useful for a known animation, but it is not a substitute for an application readiness signal.
A complete browser example
The following pattern captures a report after data has been rendered. The onclone callback changes only the cloned document used by html2canvas, so the live page is not modified.
Free tools Windows power users keep installed
One-click scans. No signup required.
#1 Best Overall
<button id="capture">Save report</button>
<section id="report">
<div class="capture-controls">Filters and buttons</div>
<div id="report-content"></div>
</section>
<script type="module">
import html2canvas from "html2canvas";
const report = document.querySelector("#report");
const captureButton = document.querySelector("#capture");
async function loadAndRenderReport() {
const response = await fetch("/api/report");
if (!response.ok) throw new Error(`Report request failed: ${response.status}`);
const data = await response.json();
document.querySelector("#report-content").textContent = JSON.stringify(data, null, 2);
// Return only after your framework has committed the update. In a
// framework, await its next-render/flush method here when necessary.
}
await loadAndRenderReport();
captureButton.addEventListener("click", async () => {
captureButton.disabled = true;
try {
const canvas = await html2canvas(report, {
useCORS: true,
onclone(clonedDocument) {
clonedDocument.querySelector(".capture-controls")?.remove();
}
});
const blob = await new Promise(resolve =>
canvas.toBlob(resolve, "image/png")
);
if (!blob) throw new Error("The browser could not create a PNG blob");
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.png";
link.click();
URL.revokeObjectURL(url);
} finally {
captureButton.disabled = false;
}
});
</script>
Install the package with your package manager (for example, npm install html2canvas) or load the browser build used by your project. The important part is not the button; it is that loadAndRenderReport() and any image/font readiness work finish before the capture call.
Waiting for images and fonts
If images affect the layout, wait for them explicitly after the dynamic update:
await Promise.all(
[...report.querySelectorAll("img")].map(img =>
img.complete
? Promise.resolve()
: new Promise(resolve => {
img.addEventListener("load", resolve, { once: true });
img.addEventListener("error", resolve, { once: true });
})
)
);
if (document.fonts?.ready) await document.fonts.ready;
const canvas = await html2canvas(report);
Resolve image errors deliberately: an image that never loads should not leave your capture promise hanging forever. For framework-driven interfaces, wait for the framework’s next paint or flush after setting state.
Target the right element and clean the clone
Pass a specific element instead of capturing document.body by default. A focused target gives predictable dimensions and avoids navigation, ads, and unrelated widgets.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #2
- Intuitive interface of a conventional FTP client
- Easy and Reliable FTP Site Maintenance.
- FTP Automation and Synchronization
const chart = document.querySelector("#sales-chart");
if (!chart) throw new Error("Sales chart is not mounted");
const canvas = await html2canvas(chart, {
width: chart.scrollWidth,
height: chart.scrollHeight
});
Use onclone(clonedDocument) for capture-only changes: remove blinking cursors, open menus, video controls, or buttons; add a print background; or switch a transient class. To exclude a node without JavaScript, add data-html2canvas-ignore:
<aside data-html2canvas-ignore>Not part of the export</aside>
The original document remains unchanged because these edits apply to html2canvas’s cloned document.
Options that matter for dynamic captures
| Option | What it controls | Practical use |
|---|---|---|
onclone |
Callback receiving the cloned document | Hide transient UI or apply export-only styles |
useCORS |
Attempts CORS-enabled image loading | Use when the remote image server sends an appropriate Access-Control-Allow-Origin response |
proxy |
Routes resource requests through a configured proxy | Use a trusted proxy when you control the server-side arrangement |
scale |
Output pixel density; the default follows device pixel ratio | Increase for sharper output, decrease to reduce memory and file size |
width, height |
Capture dimensions for the target | Set explicit dimensions when the element’s layout is larger than its visible box |
windowWidth, windowHeight |
Virtual viewport used during rendering and media queries | Match the relevant scroll dimensions when media-query or clipping behavior is wrong |
data-html2canvas-ignore |
Element-level exclusion marker | Leave controls or private UI out of the output |
Option names and defaults can change, so verify them against the current html2canvas configuration documentation when upgrading.
Fidelity limits you should design for
html2canvas does not ask the browser for its final framebuffer. It parses the DOM, reads styles, and paints what its renderer supports. Unsupported or partially supported CSS can therefore differ from what you see on screen. Complex filters, some blend modes, generated content, advanced shadows, and browser-specific effects deserve visual comparison.
Rank #3
Embedded documents follow browser security rules. Same-origin iframes can be traversed recursively. Cross-origin iframes, and sandboxed frames without allow-same-origin, cannot expose their contents to the page and will be missing.
Canvas output also consumes memory proportional to pixel dimensions. A large full-page target multiplied by a high scale can exceed browser or platform canvas limits. Symptoms include a blank or partial image, an exception during export, or a tab becoming unresponsive. Reduce scale, capture sections, set practical dimensions, or match windowWidth/windowHeight to the element’s scroll size rather than blindly enlarging the viewport. Browser limits vary, so avoid coding to a single maximum.
Cross-origin images: what works and what cannot
A remote image must grant permission with CORS headers for the browser to use it safely in an exportable canvas. useCORS: true requests that mode; it cannot override a server that denies the request. A configured proxy can fetch the asset from an origin you control and serve it with suitable headers.
allowTaint is not a bypass. A tainted canvas cannot be exported with toBlob() or toDataURL() in the way an application normally expects. If an image is not essential, omit it or provide a same-origin alternative. Check the browser Network and Console panels for CORS errors rather than debugging the canvas code first.
Recommended Free Tools
Rank #4
Export formats and delivery
PNG is lossless and suited to text, charts, and transparency. JPEG is smaller for photographic content but loses transparency and introduces compression artifacts. WebP support depends on the browser and your downstream pipeline.
const pngBlob = await new Promise(resolve => canvas.toBlob(resolve, "image/png"));
const jpegBlob = await new Promise(resolve =>
canvas.toBlob(resolve, "image/jpeg", 0.9)
);
const dataUrl = canvas.toDataURL("image/png");
Check for a null Blob: browsers can refuse an export when dimensions are invalid or the canvas is too large. Revoke object URLs after downloads and uploads to avoid retaining memory.
Troubleshooting by symptom
New content is missing
- Verify the selector points to the mounted element, not an empty template node.
- Move the call after the data promise and the framework’s render commit.
- Wait for relevant images and
document.fonts.ready. - Use
oncloneto inspect or remove an overlay that covers the intended state.
Images are absent or export fails with a security error
- Inspect the image response for CORS headers.
- Try
useCORS: trueonly when the server permits it. - Use a trusted proxy or a same-origin asset.
- Do not rely on
allowTaintto make an unsafe canvas exportable.
CSS does not match the screen
- Check whether the property is supported by html2canvas’s renderer.
- Apply a simpler capture-only style in
onclone. - Compare at the same viewport and scale; media queries can change when
windowWidthchanges.
The result is blank, clipped, or crashes the tab
- Log
scrollWidth,scrollHeight, and the chosen scale. - Capture a smaller region or lower
scale. - Set
width/heightand, where appropriate,windowWidth/windowHeightto the target’s scroll dimensions. - Test in the browsers your users actually run; canvas limits differ by platform.
You are running in Node.js
html2canvas relies on browser APIs and is not intended as a Node.js screenshot engine. For server-side output, use browser automation such as Puppeteer or Playwright, which render a real browser page. For a browser extension that needs the actual rendered viewport, use the browser’s native extension screenshot APIs.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your requirement is a real website screenshot, a server-side job, or repeatable capture outside a user’s tab, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
See the ScreenshotNeo API documentation for authentication and options. This cURL request saves a WebP image:
Best Value
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent 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)
Equivalent 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 fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Every feature is included on every plan. The Free plan includes 1,000 shots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to get an API key.
Choosing the right capture method
| Requirement | Best fit | Reason |
|---|---|---|
| Capture a component already rendered in the user’s page | html2canvas | No server round trip; can use your app state and clone-time edits |
| Pixel-accurate browser screenshot | Browser extension or automation | Captures the browser’s rendered pixels rather than reconstructing DOM |
| Server-side, repeatable URL capture | Puppeteer, Playwright, or ScreenshotNeo | Runs outside the user’s tab; ScreenshotNeo also handles consent UI and billing verdicts |
| AI agent needs screenshots or PDFs | ScreenshotNeo MCP server | Provides dedicated screenshot, page-info, and PDF tools |
Frequently Asked Questions
Can html2canvas capture a page before JavaScript finishes?
It can render whatever DOM exists at call time, but it has no way to know which asynchronous work your application considers complete. Trigger it from your own data/render completion signal.
Will a cross-origin iframe appear in the image?
No. Browser same-origin policy blocks cross-origin frame contents; same-origin frames can be processed recursively.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Does increasing scale improve accuracy?
It increases output pixel density, not CSS feature support. Higher values also increase memory use and can trigger canvas-size limits.
Can I use html2canvas in a backend worker?
Not directly. The library expects browser APIs. Use a browser automation tool or a screenshot service for backend jobs.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

