When to Capture Animated Elements with Html2canvas
Capture an animated element with html2canvas only after its target state is stable. Learn timing, animation pausing, CSS limits, cross-origin issues, and better alternatives.
Capture an animated element after it has reached the visual state you want and hold that state while html2canvas runs. Timing chooses the DOM and CSS state; html2canvas then reconstructs an image from that state. It does not take a native screenshot or promise synchronization to a particular animation frame.
For one still image, use a developer-controlled pause, a temporary static style, or an animation state that naturally remains stable. Restore the page afterward. Test the real element in the target browser because unsupported CSS, fonts, transforms, filters, pseudo-elements, cross-origin images, and iframes can affect the result regardless of timing.
What html2canvas actually captures
html2canvas builds a canvas from DOM and style information available to the page. The project documentation warns that the result may not exactly match the browser’s rendered pixels because it is not making an actual screenshot. Every CSS property must be implemented individually, so full CSS support is not possible. See the official documentation and FAQ.
That distinction matters for animation:
- A call made at the desired moment helps only when the element’s DOM and computed styles represent that moment.
- The renderer must also support the properties that create the appearance.
- A native browser capture records the rendered surface; html2canvas reconstructs it.
A reliable still-image workflow
- Define the state to preserve: for example, progress 0.65, the third carousel slide, or an expanded panel.
- Make the state deterministic. Prefer a class or data attribute over waiting an arbitrary number of milliseconds.
- Pause or hold the animation, then wait until the styles and assets you need are present.
- Call
html2canvason the element. - Restore the original animation state after the promise resolves or rejects.
<button id="capture">Capture current state</button>
<div id="animated-card" class="card">Animated contentrequestAnimationFrame in this example gives the browser a chance to apply the class. It does not guarantee a particular animation frame. If you need an exact value, set it yourself with a class, inline style, CSS custom property, or Web Animations API state, then capture.
Controlling the animation state
Pause CSS animations
.capture-freeze * {
animation-play-state: paused !important;
}
Apply this class to a capture root, wait for style application, and remove it afterward. This is a workaround you control, not an html2canvas frame-lock feature. It can also pause animations you did not intend to change.
Set a deterministic class or style
const element = document.querySelector(".progress");
element.classList.add("at-65-percent");
await new Promise(requestAnimationFrame);
const canvas = await html2canvas(element);
This approach is usually more repeatable than a timer. Keep the target state in the DOM or CSS so the cloned document contains the same values.
Use the Web Animations API when available
const animation = document.querySelector(".hero").getAnimations()[0];
animation.pause();
animation.currentTime = 650; // milliseconds; choose your intended state
await animation.finished.catch(() => {});
const canvas = await html2canvas(document.querySelector(".hero"));
animation.play();
Browser support and animation types vary. Verify the computed result visually; setting currentTime alone does not make unsupported CSS render correctly in html2canvas.
Important html2canvas options
| Option | Use | Timing or fidelity note |
|---|---|---|
scale |
Control output resolution; device-pixel ratio is common. | Higher values increase memory and canvas limits. |
width, height |
Set the cloned rendering dimensions. | Use explicit dimensions when responsive layout changes the state. |
x, y |
Crop the capture region. | Coordinates are relative to the rendered document. |
backgroundColor |
Choose a background or null for transparency. |
Transparency still depends on the element’s styles. |
useCORS |
Attempt CORS-enabled image loading. | The image server must send an appropriate CORS header. |
allowTaint |
Allow images that taint the canvas. | A tainted canvas cannot be exported with toDataURL or toBlob. |
ignoreElements |
Exclude a node from the cloned document. | Useful for blinking cursors, videos, or controls that should not appear. |
onclone |
Modify the cloned document before rendering. | Freeze animation in the clone while leaving the live page untouched. |
const canvas = await html2canvas(document.querySelector(".scene"), {
scale: 2,
backgroundColor: null,
onclone: clonedDocument => {
clonedDocument.querySelector(".scene")?.classList.add("capture-freeze");
},
ignoreElements: element => element.matches(".live-cursor, video")
});
Check the project’s configuration reference for the current option set. Options define the rendering context; they do not provide frame-accurate animation recording.
Waiting for fonts, images, and layout
Animation can appear to be at the right point while late-loading assets still change the pixels. Wait for resources that matter to your capture:
await document.fonts.ready;
await Promise.all(
[...document.images]
.filter(image => !image.complete)
.map(image => new Promise(resolve => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", resolve, { once: true });
}))
);
await new Promise(requestAnimationFrame);
const canvas = await html2canvas(target);
A font swap can change line wrapping and therefore the animation’s geometry. Images from another origin need CORS permission or they may be missing or make export impossible. Cross-origin iframes cannot be read as ordinary DOM by page JavaScript.
When html2canvas is the wrong capture method
- Exact browser pixels: use a native browser or tab capture method.
- Browser extensions: the project FAQ points to native tab-screenshot APIs, which are more reliable for that use case and do not have html2canvas’s canvas-size limits.
- Video or many animation frames: the reviewed html2canvas materials do not establish dependable continuous or frame-accurate capture. Choose browser automation or a capture tool designed for rendered frames.
Do not describe a fixed delay, one animation frame, or a Promise as a guarantee of a particular visual frame. Those mechanisms only coordinate your code with the browser; they do not create a documented html2canvas synchronization contract.
Or skip the browser setup
ScreenshotNeo captures a page with one GET request and returns PNG, JPEG, WebP, or PDF. It is useful when you need a rendered page image instead of reconstructing a DOM element in client-side JavaScript. Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf.
See the ScreenshotNeo API documentation.
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}`);
Every feature is available on every plan: 1,000 shots per month are free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The capture shows the wrong pose or slide. | The animation was still running or the state was not deterministic. | Set an explicit state, pause it, wait for style application, and capture once. |
| The animation is frozen but looks different. | html2canvas does not support a CSS property or effect as the browser does. | Check supported features, simplify the style, or use native browser capture. |
| Images are missing. | Images are not loaded or are cross-origin without CORS. | Wait for image loads and configure the image server’s CORS headers. |
toDataURL throws a security error. |
The canvas is tainted by a cross-origin image. | Use CORS-enabled assets, remove the asset, or avoid exporting that canvas. |
| Text wraps differently. | Fonts were not ready or viewport dimensions changed. | Await document.fonts.ready and set explicit dimensions. |
| The canvas is blank or too large. | Browser canvas-size or memory limits. | Lower scale, capture a smaller region, or use native capture. |
| A video frame is not captured reliably. | html2canvas is not documented as a video or frame-accurate recorder. | Use a browser capture workflow designed for video or repeated frames. |
Performance, reliability, and cost
Large full-page elements, high scale, shadows, filters, and many images increase cloning time and memory use. Capture the smallest useful element, choose a practical scale, exclude blinking or video elements, and reuse a prepared deterministic state. For repeatable output, fix the viewport, fonts, data, timezone, and asset readiness.
html2canvas runs in the browser and has no service charge, but it consumes the user’s CPU and memory and remains subject to browser security policies. ScreenshotNeo charges only for clean shots; failed loads, bot checks, blank pages, timeouts, and cache hits cost nothing. Its plans are Free (1,000/month), Starter ($5/3,000), Growth ($15/15,000), Pro ($39/60,000), Scale ($99/250,000), and Business ($249/1,000,000); yearly billing gives two months free.
FAQ
Should I wait a fixed number of milliseconds?
No. A fixed delay can work for one page and fail when network speed, device load, or animation timing changes. Hold an explicit state and wait for the resources that affect the image.
Can html2canvas capture an exact animation frame?
Its official materials do not promise frame synchronization. You can set and hold a state yourself, then verify the result, but exact browser-pixel or frame-accurate capture requires another workflow.
Does pausing the animation guarantee fidelity?
No. Pausing controls timing only. CSS support, cross-origin policies, fonts, images, and layout still determine what html2canvas can reconstruct.
Can I capture an entire animated page?
You can pass a larger root element and use full dimensions, but canvas limits and unsupported effects become more likely. For a rendered page screenshot, a browser capture service is often simpler.
What is the smallest reliable test?
Use the real element, target browser, fonts, images, and animation. Capture one known state, compare it with the browser view, then test the largest viewport and scale you will support.


