Wait for every image inside (or otherwise contributing to) your capture target to finish loading and decoding before you call html2canvas. Use img.decode() when available, treat decode failures explicitly, and check naturalWidth rather than trusting img.complete alone. Then await the promise returned by html2canvas before exporting the canvas.
This approach separates three events that are often confused: the network request finishing, the browser decoding the bitmap, and html2canvas rendering the DOM. It also gives you a deliberate policy for broken images, lazy loading, cross-origin assets and rendering limits.
The reliable sequence
A deterministic capture normally follows this order:
- Identify the exact element and all images that contribute to it.
- Make required lazy images eligible to load.
- Wait for each image to be usable, preferably with
decode(). - Decide what to do if an image fails: reject, omit, or replace it.
- Call
html2canvasand await its returned promise. - Export or otherwise use the finished canvas.
A fixed delay, DOMContentLoaded, or window.onload is not a universal guarantee. Content can be inserted after those events, and lazy images may not even start loading until they approach the viewport.
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
A production-ready JavaScript helper
The helper below waits only for images under the target element. It accepts already-successful images, uses decoding when possible, validates fallback loads, and rejects by default when an image cannot be used.
async function waitForImages(root) {
const images = [...root.querySelectorAll("img")];
await Promise.all(images.map(async (img) => {
// complete can also be true for broken or empty images.
if (img.complete && img.naturalWidth > 0) {
if (typeof img.decode === "function") {
await img.decode();
}
return;
}
// decode() waits for a usable decoded image and rejects on failure.
if (typeof img.decode === "function") {
await img.decode();
return;
}
// Older-browser fallback: wait for one terminal event, then validate.
await new Promise((resolve, reject) => {
img.addEventListener("load", resolve, { once: true });
img.addEventListener("error", () => {
reject(new Error(`Image failed: ${img.currentSrc || img.src}`));
}, { once: true });
});
if (img.naturalWidth === 0) {
throw new Error(`Image is not usable: ${img.currentSrc || img.src}`);
}
}));
}
async function capture(element) {
await waitForImages(element);
return await html2canvas(element, { imageTimeout: 15000 });
}
const target = document.querySelector("#invoice");
try {
const canvas = await capture(target);
const pngUrl = canvas.toDataURL("image/png");
// Example: document.querySelector("#preview").src = pngUrl;
} catch (error) {
console.error("Capture could not be completed", error);
}
Run the readiness check immediately before capture. If your code changes image sources, inserts markup, swaps responsive images, or triggers application rendering after the check, run it again.
Why check both complete and naturalWidth?
complete means the request has reached a terminal state, not that the resource is valid. It can be true for an image with no source or a failed request. A positive naturalWidth is a practical success check; decoding then confirms that the browser can use the bitmap.
Why prefer decode()?
decode() resolves after the image is decoded and ready for use, and rejects when decoding fails. That is closer to the state required for a consistent canvas than merely observing a network load event. Older environments without it can use load/error listeners plus the naturalWidth validation shown above.
Choosing a failure policy
There is no single correct response to a missing image. Choose according to what the capture means to your application.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
| Policy | Implementation idea | Use when |
|---|---|---|
| Reject | Allow decode() or the fallback promise to throw. |
A receipt, report or archival image is invalid without every asset. |
| Omit | Catch per-image errors, record the URL, and continue. | The screenshot is best-effort and missing thumbnails are acceptable. |
| Replace | Set a known fallback source or render a placeholder, then wait again. | The layout must remain complete even when an origin is temporarily unavailable. |
If you continue after an error, log which image failed. Otherwise a visually incomplete canvas can look like a successful capture.
Lazy-loaded images and changing content
An image with loading="lazy" may defer its request while it is far outside the viewport. Waiting on such an element before making it eligible to load can wait forever or report a failure unrelated to the image URL.
Make required images load
- Scroll the target (or the page) so required images intersect the viewport, then wait.
- For a capture-only render, temporarily change required images to eager loading before the readiness check.
- Use an application-specific lazy-load trigger, then run
waitForImagesafter the trigger has completed.
Include nested content that contributes to the final capture. If your clone or layout pulls assets from outside the selected subtree, query and wait for those assets too; checking only root.querySelectorAll("img") cannot discover unrelated elements.
html2canvas settings and what they do
imageTimeout
The html2canvas configuration reference documents a default imageTimeout of 15,000 milliseconds. It is a timeout for image loading, not a guarantee that an image will succeed. Setting it to 0 disables that timeout. Check the configuration for the exact html2canvas version installed because defaults and supported options can vary by release.
onclone
Use onclone when you need to modify the cloned document used for rendering—for example, to reveal a print-only section or adjust styles. If that callback changes image sources or inserts images, perform readiness work on the changed clone or arrange the changes before your own wait. The original page’s image check does not automatically cover new clone content.
Rank #3
useCORS and proxy
useCORS asks the browser to request remote images with CORS. The remote server must send a permitting CORS response; the option cannot grant permission by itself. A configured proxy is another documented route for obtaining images through an origin you control.
allowTaint: true is not an export fix. An origin-tainted canvas may still be rendered, but browser security prevents readback operations such as toDataURL() or pixel access.
Cross-origin, cache and security edge cases
- Loaded does not mean readable: an image can display successfully yet taint the canvas when its origin is not permitted.
- Credentials matter: cookies, signed URLs and authentication headers must be available to the browser request; html2canvas cannot infer access that the page does not have.
- Cache changes timing: a cached image may complete immediately, while a cold request may exceed the timeout. Keep the explicit readiness check for both cases.
- Changing
src: wait only after the final source andsrcsetchoice have been applied. - Dynamic frameworks: wait after the framework has committed the final DOM, not merely after the initial script tag executes.
Common failures and fixes
Images are missing even though the page looks loaded
Cause: the capture ran before decode, or images were lazy and never requested. Fix: trigger lazy loading, await the helper, and capture only after the final DOM update.
The helper hangs or rejects on one URL
Cause: a broken source, decode failure, or an image that never emits a terminal event. Fix: record currentSrc, verify the URL in the browser, add an application timeout around your own wait if needed, and apply your chosen reject/omit/replace policy.
complete is true but the result is blank
Cause: complete also covers broken or empty images. Fix: require naturalWidth > 0 and use decode() where available.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
The image appears, but toDataURL() throws a security error
Cause: the canvas is tainted by a cross-origin image. Fix: configure server-side CORS and useCORS, or route the asset through a suitable proxy. Do not rely on allowTaint for readable output.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The screenshot still differs from the browser
Cause: this is a rendering-fidelity issue, not necessarily an image-timing issue. html2canvas reconstructs a representation from DOM and supported CSS; it does not capture the browser’s native pixels. Unsupported CSS, canvas-size limits and complex effects can remain different after every image is ready.
Performance and reliability practices
- Scope the query to the actual target instead of waiting for every image on a large page.
- Use
Promise.allso independent images wait concurrently. - Collect failures with their URLs so retries are targeted rather than repeating the entire workflow blindly.
- Wait again after any layout or source mutation immediately before capture.
- Keep html2canvas’s timeout as a safety boundary, while treating your readiness policy as the definition of “good enough.”
Or skip the browser setup
For server-side jobs or repeatable automation, ScreenshotNeo provides a one-request website screenshot API. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, blank pages, failed loads and timeouts are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server lets Claude, Cursor and other MCP clients call take_screenshot, get_page_info and capture_pdf.
Use the ScreenshotNeo documentation for parameters and response details.
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}`);
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to start.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →FAQ
Should I wait for every image on the page?
No. Wait for every image that can affect the selected capture, plus any external content your rendering process includes. Waiting for unrelated page images only adds delay.
Best Value
Can I use a fixed three-second delay instead?
A delay is only a guess. It may be excessive on a fast connection and insufficient for a slow or newly inserted image. Readiness checks observe the actual resources you need.
Does awaiting html2canvas replace the image wait?
No. Awaiting html2canvas waits for its rendering promise; your preceding check defines which images must already be usable before rendering begins.
Frequently Asked Questions
What does a successful image wait guarantee?
It confirms that the selected images reached a usable, decoded state under your chosen failure policy. It does not guarantee CORS readback or perfect CSS fidelity.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesWhere should I put the readiness check in a component-based app?
Run it after the component has rendered its final capture DOM and after any lazy-load trigger, immediately before invoking html2canvas.
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.

