Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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:

  1. Wait for your own data request and rendering code to complete.
  2. Select the specific component or region to capture.
  3. Call html2canvas(element, options) and await the returned Promise.
  4. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Free Fling File Transfer Software for Windows [PC Download]
  • 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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 onclone to 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: true only when the server permits it.
  • Use a trusted proxy or a same-origin asset.
  • Do not rely on allowTaint to 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 windowWidth changes.

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/height and, where appropriate, windowWidth/windowHeight to 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.Support on Ko-Fi

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo API documentation for authentication and options. This cURL request saves a WebP image:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.