If html2canvas requests the same images on every iteration, keep one shared cache for the lifetime of the capture job and set clearImageCache: false. Do not create a new cache inside the loop. If memory must be bounded, use the version-supported maxCacheSize option rather than clearing the entire cache after every capture.
Why html2canvas loads images again
Each html2canvas() call creates a rendering context. Resource options and an optional image cache are passed into that context. A loop can therefore lose reuse in two ways: the code explicitly clears the cache, or a wrapper creates a fresh cache or rendering state for every iteration.
Look first for clearImageCache: true. The html2canvas configuration reference says to leave this setting false to keep images cached across calls, and warns against enabling it when a cache is shared by concurrent captures. Setting it to true in a loop deliberately discards the reusable image state before the next capture.
Even with clearing disabled, a new URL is a new cache key. Query-string cache busters, changing CSS background-image values, signed URLs, redirects and dynamically inserted image nodes can all make the next iteration request a different resource.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems#1 Best Overall
Use one cache for sequential captures
Keep cache state outside the loop and pass the same instance to each call. The cache constructor and injection option are version-dependent, so confirm that your installed html2canvas release publicly exposes them before using this pattern. The stable setting is clearImageCache: false.
const sharedCache = new CacheStorage(); // only where your installed version exposes this API
for (const frame of frames) {
const canvas = await html2canvas(frame.element, {
cache: sharedCache,
clearImageCache: false,
maxCacheSize: 200,
onclone: (clonedDocument) => {
clonedDocument
.querySelectorAll('[data-html2canvas-ignore="true"]')
.forEach((node) => node.remove());
}
});
consume(canvas);
}
This example is deliberately sequential: the next capture starts after the previous promise resolves. It gives the shared cache a stable owner and makes network-panel comparisons easier. If your release does not support cache, do not copy the constructor from another version or fork; keep clearImageCache: false and consult that release’s public options for the supported caching mechanism.
Do not construct the cache in the loop
This defeats reuse even though the option appears correct:
for (const frame of frames) {
const cache = new CacheStorage();
await html2canvas(frame.element, {
cache,
clearImageCache: false
});
}
Search helper functions as well as the visible loop. A utility that silently allocates a cache, document clone manager or renderer on each call has the same effect as the bad example.
Recommended Free Tools
Rank #2
Limit memory without flushing every image
A long-running capture process can retain many decoded images. Where the installed version supports it, maxCacheSize provides a ceiling and allows least-recently-used entries to be evicted. That is different from clearing everything after every frame: frequently reused images can remain available while old entries make room for new ones.
| Option | Setting or default | What it changes |
|---|---|---|
clearImageCache |
false for reuse |
Preserves the shared image cache between calls. Setting it true in the loop removes the main benefit of sharing. |
maxCacheSize |
Choose a limit where supported | Bounds retained cache entries through least-recently-used eviction; availability is version-dependent. |
removeContainer |
true by default |
Removes temporary cloned DOM after rendering. Disabling cleanup does not stop image requests and can retain more DOM memory. |
useCORS |
false by default |
Requests cross-origin images with CORS when enabled, but only succeeds if the final response permits your origin. |
proxy |
null by default |
Uses a same-origin proxy when direct cross-origin loading cannot satisfy browser content-policy rules. |
imageTimeout |
15,000 milliseconds | Stops waiting for an image after the documented default timeout. |
Choose a limit based on the number and dimensions of images your job actually captures. A small limit can cause older images to be fetched again later; an unlimited cache can increase memory pressure. Measure both network requests and process memory instead of assuming that a larger cache is always faster.
Make the cloned document stable
onclone runs against the document clone used for rendering, not the live page. Use it to remove volatile or nonessential nodes, replace changing URLs, or make each iteration’s resource set deterministic without modifying what users see.
Remove widgets and decorative nodes
For one-off exclusions, mark elements in the source markup:
<div data-html2canvas-ignore="true" class="live-chat">...</div>
html2canvas ignores that element during capture. You can also use the ignoreElements predicate when the rule belongs in JavaScript:
await html2canvas(element, {
clearImageCache: false,
ignoreElements: (node) => node.matches('.live-chat, .newsletter-popup')
});
Filtering reduces the resources html2canvas has to inspect, but it can reduce visual fidelity if the excluded node is part of the intended result. Keep the rule limited to items that should not appear in the image.
Normalize changing URLs in onclone
If every frame appends a timestamp or cache-busting query parameter, the browser and html2canvas see a different URL each time. In the clone, replace that volatile value with a stable URL when the image content is meant to remain the same:
const stableCaptureOptions = {
clearImageCache: false,
onclone: (doc) => {
doc.querySelectorAll('img').forEach((img) => {
const url = new URL(img.src, doc.baseURI);
url.searchParams.delete('cacheBust');
url.searchParams.delete('timestamp');
img.src = url.href;
});
}
};
Apply the same reasoning to CSS background-image values and dynamically added nodes. Do not normalize a URL when its query parameters select genuinely different content.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallRank #4
Cross-origin images, CORS and redirects
html2canvas cannot bypass browser content-policy restrictions. Set useCORS: true only when the image server sends an appropriate Access-Control-Allow-Origin header. If it does not, fetch the image through a proxy that serves it from the page’s origin.
A URL that appears same-origin can still redirect to a CDN. An html2canvas issue reports that origin classification may happen before the redirect, so CORS handling might not be applied to the final CDN request. Treat this as a diagnostic possibility, not as an API guarantee or a reason to patch internals.
- Open the browser Network panel while capturing one frame.
- Inspect the final request URL, every redirect, the response status and the response’s
Access-Control-Allow-Originheader. - Compare those values on the second iteration. If the final host or query string changes, the cache key may also change.
- Use a same-origin proxy when the final response cannot authorize your page’s origin.
Sequential versus concurrent loops
Sequential captures are the safest place to start because one shared cache has a single, predictable owner. They also prevent your code from clearing a cache while another capture is using it.
If you need concurrency, verify the installed release’s cache behavior and synchronization guarantees first. Never combine shared-cache concurrency with clearImageCache: true; that setting can discard resources another capture still expects. A bounded cache may also evict an image before a concurrent job reuses it, so measure request counts under the actual workload.
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
Debugging checklist
- Requests repeat immediately: search for
clearImageCache: truein the call and in wrapper functions; change it to false or remove the override. - The cache option appears correct but nothing is reused: confirm that every iteration receives the same cache object and that the installed version supports cache injection.
- URLs differ between frames: compare image
srcvalues, CSS background URLs and query parameters; stabilize only values that do not select different content. - Chat, ads or popups create extra requests: remove them in
onclone, useignoreElements, or adddata-html2canvas-ignore="true". - Images are blank or the canvas is tainted: inspect the final response for
Access-Control-Allow-Origin; enableuseCORSonly with server cooperation, otherwise use a same-origin proxy. - A same-origin URL fails after a redirect: inspect the CDN destination and its headers rather than only the original URL.
- Memory grows during a long job: keep the shared cache, enable a supported
maxCacheSize, and leaveremoveContainerat its default cleanup behavior unless you have a specific reason to retain clones. - Images time out: remember that the documented default
imageTimeoutis 15,000 ms; verify the resource’s response time and adjust the option only when a longer wait is justified. - Documentation does not match your code: check the exact installed html2canvas release. Options exposed by a fork or a newer version are not automatically available in yours.
Performance and reliability practices
Reuse stable URLs, remove resources that do not belong in the capture, and keep temporary containers cleaning up. These changes reduce both network work and clone overhead without changing the live page. Record the request URL, redirect chain, cache status and response headers for at least two successive iterations; a visual match alone does not prove that no request occurred.
For repeatable output, capture the same element dimensions and avoid changing layout while a render is in progress. If the page intentionally changes an image between frames, expect a new request or cache entry; caching cannot make different content identical.
Or skip the browser setup
For server-side or automated screenshots, ScreenshotNeo provides a website screenshot API and MCP server. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info and capture_pdf—work with Claude, Cursor and other MCP clients.
One GET request returns a PNG, JPEG, WebP or PDF. The parameter names used by other screenshot APIs also work, which can simplify migration. See the ScreenshotNeo documentation for the complete option list.
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 →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
import { writeFile } from 'node:fs/promises';
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}`);
await writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan, and annual billing gives two months free. Create a free ScreenshotNeo account to try the endpoint.
Frequently Asked Questions
Will the shared html2canvas cache survive a page reload?
No. The in-memory cache belongs to the current JavaScript execution. A reload creates a new page and a new cache, so persistent reuse would require a separate browser or application-level storage design.
Does a browser HTTP 304 response prove html2canvas reused its image cache?
No. A 304 or memory-cache result describes browser networking, while html2canvas may still decode or process the image for its renderer. Check both the Network panel and the cache configuration.
Can I use one cache for unrelated pages?
Only when the cache API exposed by your installed version supports that ownership model and the resource keys are safe to share. Keep the cache scoped to a capture job unless the package documentation explicitly supports broader sharing.
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.

