If html2canvas() appears to stall, first determine whether its Promise has returned. When html2canvas logs Finished rendering and returns an HTMLCanvasElement, the delay is in your code after rendering—often serialization, upload, download, image insertion, or a large UI update. If that boundary is never reached, investigate resource loading, the cloned DOM, target dimensions, and render work.
There is no single confirmed cause for every “stuck after rendering” report. The sequence below isolates the failing stage without guessing at a particular bug.
1. Add a hard completion boundary
Instrument the call before changing options. This tells you whether the renderer is waiting or whether your next operation is the apparent freeze.
console.time('html2canvas');
const canvas = await html2canvas(element, {
logging: true,
onError: (error) => console.warn('html2canvas resource failed:', error.message),
});
console.timeEnd('html2canvas');
console.log('canvas returned', canvas.width, canvas.height);
Compare your logs with html2canvas’s own Finished rendering message. If both appear, html2canvas has completed. Temporarily comment out or separately time everything that follows, including toDataURL(), toBlob(), adding an image to the DOM, an upload request, a download, and large state updates. A page that becomes unresponsive after the return boundary is a caller-side problem, not an unresolved rendering Promise.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
If your completion log never appears, leave logging: true enabled and continue with the checks below. The onError callback reports resource failures while rendering continues, so it is useful even when it does not stop the operation.
2. Prove which stage is slow
Check the input and clone
Capture a small, static element first. If that succeeds, the problem is related to the original subtree’s size, resources, styles, or scripts rather than the basic API call. Use onclone to inspect or simplify the temporary document without modifying the live page.
const canvas = await html2canvas(element, {
logging: true,
onclone: (clonedDocument) => {
console.log('clone created', clonedDocument.body?.childElementCount);
const cloneTarget = clonedDocument.querySelector('#capture');
if (cloneTarget) cloneTarget.classList.add('debug-capture');
},
onError: (error) => console.warn('resource failed:', error),
});
Do not treat removeContainer: true as a general hang fix. It removes the temporary cloned DOM after capture; it does not repair a blocked resource, an oversized canvas, or work performed after the Promise resolves.
Time work you control
Measure resource readiness, the call itself, and post-processing separately. For example:
Recommended Free Tools
console.time('assets-ready');
await document.fonts.ready;
console.timeEnd('assets-ready');
console.time('render');
const canvas = await html2canvas(element, { logging: true });
console.timeEnd('render');
console.time('serialize');
const blob = await new Promise((resolve, reject) =>
canvas.toBlob((value) => value ? resolve(value) : reject(new Error('toBlob returned null')), 'image/png')
);
console.timeEnd('serialize');
This does not assume that any one phase is at fault. It gives you a reproducible boundary to report with the browser, operating system, html2canvas version, target dimensions, and log output.
Rank #2
3. Check canvas dimensions before blaming the renderer
Canvas limits vary by browser and platform. A very large result can be blank, partial, or fail to behave normally without a useful JavaScript exception. Record the target’s dimensions and the resulting canvas dimensions:
console.log({
scrollWidth: element.scrollWidth,
scrollHeight: element.scrollHeight,
clientWidth: element.clientWidth,
clientHeight: element.clientHeight,
devicePixelRatio: window.devicePixelRatio,
});
For a long element, the FAQ’s documented pattern is to render using its scroll dimensions:
const canvas = await html2canvas(element, {
windowWidth: element.scrollWidth,
windowHeight: element.scrollHeight,
});
windowWidth and windowHeight describe the virtual window used during rendering and can change responsive media-query results. They do not guarantee that a browser can allocate the resulting bitmap.
Free tools Windows power users keep installed
One-click scans. No signup required.
scale defaults to the browser’s device-pixel ratio. As a diagnostic, lower it or capture a smaller region:
const canvas = await html2canvas(element, {
scale: 1,
width: Math.min(element.scrollWidth, 1600),
});
Reducing scale lowers pixel count and memory demand, but it also lowers output resolution. If a small element works and the full page does not, split the capture into sections or use a capture method designed for full browser output instead of assuming a particular html2canvas defect.
4. Resolve cross-origin images and redirects
html2canvas reconstructs a page from DOM and CSS information. Browser same-origin rules still apply; the library cannot bypass them. With the default allowTaint: false, images that would taint the canvas are skipped.
Use CORS only when the image server supports it
const canvas = await html2canvas(element, {
useCORS: true,
onError: (error) => console.warn('image or resource failed:', error.message),
});
useCORS: true works only when the remote response supplies an appropriate CORS header. Inspect the browser Network panel for the final response, not merely the URL in your markup. A same-origin URL can redirect to another host; a reported January 17, 2023 case involved a redirect to a CDN where the user’s CORS setup did not behave as expected. That individual report is not proof of a universal bug or a confirmed fix.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Use a proxy when you control the capture path
If the remote server cannot grant CORS access, route the image through a proxy that returns the required headers, then capture the proxied URL. Do not enable allowTaint: true as a blind workaround: a tainted canvas cannot be safely read back for many export operations.
Find the failing resource
Keep onError enabled, inspect failed and redirected requests, and test the target after removing remote images one at a time. A failed image normally does not explain a Promise that has already returned; it explains missing content or a render path that is still waiting on resources.
5. Handle repeated captures safely
Long-lived applications can perform many captures in one page. The configuration includes clearImageCache for releasing shared image-cache memory and maxCacheSize for bounding that cache. Consider these options only when the symptom appears after repeated captures, not as a first response to one slow call.
Rank #4
Do not clear a cache that is shared by concurrent captures. Coordinate capture jobs so one operation cannot invalidate resources another operation is still using. If captures run sequentially, measure memory and duration before and after changing cache settings.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →6. Know what html2canvas can and cannot capture
html2canvas is not a native screenshot API. It builds a representation from DOM and CSS data, so unsupported CSS may be absent and output may differ from what the browser paints pixel-for-pixel. It also cannot read the contents of a cross-origin iframe because of browser security restrictions.
Browser-extension screenshots
When you are writing a browser extension and need the actual rendered tab, use the browser’s native screenshot capability, such as chrome.tabs.captureVisibleTab() or browser.tabs.captureVisibleTab(). This is a different execution context and has its own permission, visibility, and size constraints.
Server-side screenshots
For server-side generation, use a real headless browser through Puppeteer or Playwright. That approach runs page scripts and browser layout rather than reconstructing the DOM inside the page that is being captured. It is appropriate when the capture must run away from the user’s browser, but it requires browser automation infrastructure.
7. Option reference for a controlled diagnosis
| Option | What it does | Diagnostic use |
|---|---|---|
logging: true |
Enables html2canvas debug logging. | Shows whether rendering reaches its completion message. |
onError |
Receives notification when a resource fails to load or render; rendering continues. | Surfaces image and resource failures that are otherwise easy to miss. |
onclone |
Lets you modify the cloned document. | Disable animations or inspect the clone without changing the original DOM. |
removeContainer: true |
Removes temporary cloned DOM elements after capture. | Cleanup only; not a general Promise-hang remedy. |
scale |
Sets output pixel density; defaults to device pixel ratio. | Lower it to test memory and canvas-size pressure. |
windowWidth, windowHeight |
Set virtual window dimensions used for rendering. | Reproduce responsive layouts or capture an element’s scroll area. |
clearImageCache, maxCacheSize |
Manage the shared image cache. | Use for measured memory issues across repeated captures; protect concurrent jobs. |
8. Troubleshooting by symptom
| Symptom | Likely boundary to inspect | Next action |
|---|---|---|
Finished rendering appears, then the UI freezes |
Caller code after the Promise | Time serialization, DOM insertion, upload, download, and state updates separately. |
| No completion message and remote images are missing | Resource loading or browser policy | Use onError, inspect final Network responses, and configure CORS or a proxy only where permitted. |
| Small elements work; a long page is blank or partial | Canvas dimensions, scale, or memory | Log dimensions, lower scale, set scroll-based window dimensions, or capture sections. |
| First capture works; later captures slow down | Shared image cache or accumulated application work | Measure memory, review maxCacheSize, and never clear a cache used by concurrent captures. |
| Output differs from the visible page | DOM/CSS reconstruction limits | Check whether the CSS, iframe, or browser feature is unsupported; choose a native or headless-browser method when fidelity is required. |
| Only one browser or platform fails | Platform-specific canvas or security limits | Record browser version, operating system, dimensions, scale, and a minimal reproduction before changing code. |
9. Or skip the browser setup
If you need a website image rather than an in-page DOM reconstruction, ScreenshotNeo is a website screenshot API and MCP server. It accepts a URL and returns PNG, JPEG, WebP, or PDF; its clean-capture flow accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture. Only clean shots are billed: bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers.
One GET request is enough:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for the complete parameter list. The same request in Python is:
Best Value
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(`ScreenshotNeo returned ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
For AI workflows, its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Other controls include full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, arbitrary viewports, retina scale, PDF paper and page-range settings, custom CSS and JavaScript, clicks before capture, selector hiding, waits, request and resource blocking, custom headers/cookies/user agents, Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for 100 URLs per call, a usage API, and an OpenAPI specification. Parameter names used by other screenshot APIs are accepted to ease migration.
| Plan | Included shots | Price |
|---|---|---|
| Free | 1,000 per month | $0; no card |
| Starter | 3,000 | $5 |
| Growth | 15,000 | $15 |
| Pro | 60,000 | $39 |
| Scale | 250,000 | $99 |
| Business | 1,000,000 | $249 |
Yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots a month without a card.
10. A compact decision checklist
- Did the Promise return, and did you record canvas width and height?
- Does the slowdown occur in rendering or in serialization, upload, or UI work afterward?
- Can a small element render successfully?
- Are remote images permitted by CORS, or do they need a proxy?
- Are dimensions and device-pixel scale within your browser’s practical canvas limits?
- Does the issue appear only after repeated or concurrent captures?
- Would a native extension screenshot or a headless browser better match the required fidelity and execution environment?
Frequently Asked Questions
What should I include in a reproducible bug report?
Include the exact html2canvas version, browser and operating-system versions, target dimensions, device-pixel ratio, enabled options, console logs, whether “Finished rendering” appears, and a minimal page that reproduces the behavior.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsCan a successful canvas still produce no downloadable file?
Yes. A returned canvas proves the rendering boundary was reached, not that later encoding, upload, or download code succeeded. Instrument those operations independently.
Is there one universal maximum canvas size I can rely on?
No. Practical limits vary by browser and platform, so test the dimensions and scale used by your deployment rather than treating an approximate limit as a guarantee.
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.

