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

To make html2canvas output repeatable, make every rendering input deterministic: fix the scale and viewport, wait for fonts and images, freeze dynamic content in onclone, exclude intentionally changing elements, and control cross-origin assets. Capture only after the returned promise resolves. These steps reduce layout and pixel drift, but html2canvas reconstructs an image from the DOM rather than taking a native compositor screenshot, so it cannot promise identical pixels for every browser feature.

What “consistent” means for html2canvas

A repeatable capture has the same canvas dimensions, element geometry, text metrics, image content, colors and state each time it runs. That is the standard needed for visual-regression tests, generated reports and cacheable exports.

html2canvas reads the DOM and computed styles, then paints its own representation. It does not ask the browser for the already-composited screen. Consequently, differences in fonts, media-query breakpoints, device-pixel ratio, animation time, timers, random values, scroll position and resource availability can change the result. The project documentation cautions that a screenshot is “based on the DOM” and may not be “100% accurate to the real representation.”

The workflow below makes the inputs explicit. It cannot make an inaccessible cross-origin iframe available, and it cannot guarantee native-browser pixel identity for features html2canvas does not reconstruct.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Build a deterministic capture

1. Freeze the capture geometry

Capture the same element and specify the dimensions and coordinates used by your test. Set windowWidth and windowHeight so responsive breakpoints do not change. Set width, height, x and y when the target must have a fixed rectangle. Keep scrollX and scrollY constant; otherwise fixed-position headers and sticky controls can move.

Do not rely on the test runner’s host viewport or on whatever scroll position a previous test left behind. A CSS-pixel viewport of 1280 by 720 and zero scroll is a practical baseline, but choose values that represent your application and keep them unchanged between runs.

2. Choose a fixed scale

The documented default for scale is window.devicePixelRatio. That value differs between a laptop display, a headless browser and a high-density monitor, so the same CSS layout can produce canvases with different pixel dimensions. Set scale: 1 for one output pixel per CSS pixel, or choose another fixed value shared by every test worker.

3. Wait for fonts before measuring or painting

Use the browser’s font readiness promise before invoking html2canvas:

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.
await document.fonts.ready;

Also ensure the intended web-font files have successfully loaded. A fallback font changes glyph widths, line wrapping and element heights; capturing a few milliseconds earlier can therefore change the entire downstream layout.

4. Wait for every image to load and decode

An image can report that its request completed while decoding is still pending. Resolve both cases before capture, and make the timeout an explicit policy. The following helper waits for already-complete images to decode and waits for load or error on the rest. An error is resolved rather than hanging the test, so diagnostics can report the missing asset.

Rank #2
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • 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
async function waitForImages(images = [...document.images]) {
  await Promise.all(images.map(img => {
    if (img.complete) {
      return img.decode?.().catch(() => {});
    }
    return new Promise(resolve => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
}

Call it after the page has inserted all expected images. html2canvas’s imageTimeout is documented as 15,000 milliseconds by default; set it deliberately when your application has a different loading budget.

5. Freeze changing state in onclone

html2canvas clones the document before rendering. Use onclone to modify that clone, leaving the production DOM untouched. Replace timestamps, random identifiers, live counters, rotating carousel content, caret or focus styling, animation classes and network-populated placeholders with stable values. For example, mark volatile nodes with data-volatile and replace their text in the clone.

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

6. Exclude content that is supposed to vary

Ads, clocks, cursor indicators, video overlays and live chat should not participate in a pixel comparison unless they are the subject of the test. Add data-html2canvas-ignore to such nodes, or provide an ignoreElements function. Excluding a deliberately variable element is more reliable than trying to predict its current frame.

7. Make external images CORS-safe

Set useCORS: true only when the image server sends a suitable Access-Control-Allow-Origin response. Otherwise the browser may skip the image or leave the canvas tainted, preventing a later toDataURL or toBlob. If you cannot change the remote server, fetch the asset through a same-origin proxy that applies the required headers and security checks. CORS does not grant access to a cross-origin iframe’s document; browser same-origin rules still prevent html2canvas from rendering that iframe’s contents.

8. Set the background and export format explicitly

The documented default backgroundColor is #ffffff. Set it explicitly for opaque regression images. Use null only when transparency is intentional, and make that choice part of the test contract. Keep logging: true while diagnosing resource problems; disable verbose logging in normal production captures after the issue is understood. Call toBlob or toDataURL only after the html2canvas promise fulfills.

A complete deterministic JavaScript pattern

This example combines fixed geometry, readiness waits, cloned-state normalization, filtering and a PNG export. Adapt the selector and volatile-element rules to your page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
async function waitForImages(images = [...document.images]) {
  await Promise.all(images.map(img => {
    if (img.complete) return img.decode?.().catch(() => {});
    return new Promise(resolve => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
}

async function captureDeterministically() {
  await document.fonts.ready;
  await waitForImages();

  const target = document.querySelector('#capture');
  if (!target) throw new Error('Missing #capture element');

  const canvas = await html2canvas(target, {
    scale: 1,
    windowWidth: 1280,
    windowHeight: 720,
    width: 1280,
    height: 720,
    x: 0,
    y: 0,
    scrollX: 0,
    scrollY: 0,
    backgroundColor: '#ffffff',
    useCORS: true,
    imageTimeout: 15000,
    onclone: clonedDoc => {
      clonedDoc.querySelectorAll('[data-volatile]').forEach(el => {
        el.textContent = '[frozen]';
        el.classList.remove('is-animating', 'has-focus-effect');
      });
      clonedDoc.querySelectorAll('video').forEach(video => video.pause());
    },
    ignoreElements: el => el.matches('.clock, .ad, .cursor, .live-chat')
  });

  const blob = await new Promise((resolve, reject) => {
    canvas.toBlob(result => result ? resolve(result) : reject(new Error('PNG encoding failed')), 'image/png');
  });
  return blob;
}

captureDeterministically().then(blob => {
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = 'capture.png';
  link.click();
  URL.revokeObjectURL(link.href);
}).catch(console.error);

The fixed width and height in this sample assume that the target is intended to occupy that rectangle. If your target is smaller or full-page, derive those values once from a controlled layout and reuse them; do not let each run choose a different bounding box.

Diagnose a mismatch systematically

When two outputs disagree, compare the following in order. Record them with each test artifact so a failure is explainable rather than anecdotal.

Axis What to inspect Typical correction
Canvas size Pixel width and height, plus the configured scale Fix scale, width and height; do not inherit device-pixel ratio
Viewport and scroll windowWidth, windowHeight, scrollX, scrollY Use the same values for every worker and reset scroll before capture
Typography Computed font family, loaded font files, font readiness Await document.fonts.ready and fix failed font requests
Images Request status, decode completion and CORS response headers Await image readiness; enable CORS only with server support or use a same-origin proxy
DOM state Timestamps, random values, animation progress, live data Normalize the clone in onclone or ignore the node
Environment Browser version, operating system, device-pixel ratio and rendering backend Pin the browser/test image and keep workers homogeneous

Common failure modes and fixes

The canvas changes size between runs

Cause: the default scale follows device-pixel ratio, or responsive CSS sees different viewport dimensions.
Fix: set a numeric scale, fixed window dimensions and explicit target dimensions. Verify the resulting canvas width and height before comparing pixels.

Text wraps differently or appears in a fallback font

Cause: capture started before web fonts were ready, or a font request failed.
Fix: await document.fonts.ready, inspect the computed font family and verify that the same font files are available in every environment.

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

Images are missing, blank or make export fail

Cause: an image was not loaded or decoded, or its origin is not permitted by CORS.
Fix: wait for load and decode, set a deliberate imageTimeout, inspect network responses, and configure the image server or a same-origin proxy. useCORS: true cannot bypass a server that omits the appropriate header.

A clock, carousel or cursor causes tiny pixel differences

Cause: the DOM contains time-dependent or animated state.
Fix: replace the value in onclone, pause animation/video in the clone, or exclude the element with ignoreElements or data-html2canvas-ignore.

An embedded frame never appears

Cause: the frame is cross-origin and its contentDocument is inaccessible under browser security rules.
Fix: render content you control from the same origin, capture the frame through a service that can access it legitimately, or use a native browser screenshot of the page rather than trying to reconstruct the frame in html2canvas.

Debugging reports an error but still produces an image

Keep logging enabled and attach an onerror handler to record failed resources while investigating. The renderer can continue after reporting a resource error, so treat the output as suspect until the missing asset is resolved or intentionally excluded.

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.

When html2canvas is the wrong boundary

Use html2canvas when you need a client-side, DOM-driven image and can control the page state. If the requirement is an exact view of browser compositor output—including inaccessible frames, browser-native controls or features html2canvas does not reconstruct—use a native browser screenshot API instead. Keep the same determinism principles: pin the viewport and browser, wait for fonts and network resources, disable animation, and define how dynamic content is handled.

Best Value
Sale
JavaScript and jQuery: Interactive Front-End Web Development
  • JavaScript Jquery
  • Introduces core programming concepts in JavaScript and jQuery
  • Uses clear descriptions, inspiring examples, and easy-to-follow diagrams
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

For a server-side screenshot, ScreenshotNeo accepts one GET request and returns PNG, JPEG, WebP or PDF. Its cleanup steps accept cookie and consent banners before capture and remove more than 60 known consent platforms, newsletter popups and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page and billing result with X-Page-Verdict and X-Billed headers.

See the complete parameter reference in the ScreenshotNeo documentation. The same endpoint supports fixed viewports and device presets, full-page lazy-image loading, CSS-selector element capture, dark mode, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, waits, hidden selectors, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparency, resizing, chosen cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification.

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}`);

ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. 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.

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

Cost and reliability considerations

  • For in-browser tests, the main cost is test runtime and the maintenance burden of controlling state; deterministic waits prevent flaky retries but should not be unbounded.
  • Use a fixed browser and device-pixel ratio in CI. A different browser build or rendering environment can alter antialiasing even when DOM values match.
  • Keep diagnostic logs and failed-resource details with visual-regression artifacts. This distinguishes a real UI change from a missing font or image.
  • Do not compare compressed formats when exact pixels matter. Encode PNG consistently and compare images at identical dimensions.
  • Cache only after inputs are stable. A cache can hide a resource change during debugging; record the cache policy and invalidate it when page state or assets change.

FAQ

Does setting scale: 1 guarantee identical screenshots?

No. It fixes the pixel-to-CSS-pixel ratio, but fonts, images, dynamic DOM state, browser rendering and cross-origin resources must also be controlled.

Should I use toDataURL or toBlob?

Either can export a completed canvas. toBlob is generally preferable for a file workflow because it avoids placing the entire encoded string in JavaScript memory; the determinism requirement is to call it only after html2canvas resolves.

Can html2canvas capture a cross-origin iframe if useCORS is enabled?

No. useCORS concerns image resources. Same-origin browser rules still prevent reading a cross-origin iframe document.

Frequently Asked Questions

How do I make a visual-regression test fail for missing assets instead of comparing a partial image?

Treat image and font readiness as test prerequisites: collect failed requests in your diagnostics hook, assert that required assets loaded, and only then compare the PNG. Use intentional exclusions for assets that are allowed to be absent.

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

What should be stored with each baseline image?

Store the canvas dimensions, scale, viewport and scroll values, browser version, device-pixel ratio, font-load result and a list of ignored or normalized selectors. That metadata makes a mismatch reproducible.

Quick Recap

SaleBestseller No. 1
HTML and CSS: Design and Build Websites
HTML and CSS: Design and Build Websites
HTML CSS Design and Build Web Sites; Comes with secure packaging; It can be a gift option
$15.75
SaleBestseller No. 2
Web Design with HTML, CSS, JavaScript and jQuery Set
Web Design with HTML, CSS, JavaScript and jQuery Set
Brand: Wiley; Set of 2 Volumes
$35.05
SaleBestseller No. 3
SaleBestseller No. 5
JavaScript and jQuery: Interactive Front-End Web Development
JavaScript and jQuery: Interactive Front-End Web Development
JavaScript Jquery; Introduces core programming concepts in JavaScript and jQuery; Uses clear descriptions, inspiring examples, and easy-to-follow diagrams
$24.04

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.