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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To make html2canvas output repeatable, make every rendering input explicit and wait for every asynchronous resource before capturing. Fix the viewport, canvas scale, element bounds, scroll offsets, fonts, images, background, and dynamic data; freeze or remove intentionally changing elements in onclone; and export only after the capture promise resolves. This controls the inputs html2canvas can reconstruct, although it cannot promise pixel identity with a browser’s native compositor.

What “consistent” means with html2canvas

html2canvas does not copy the browser’s final composited framebuffer. It reads the DOM, computed styles, and available resources, then paints an approximation into a canvas. The project documentation cautions that “The screenshot is based on the DOM and as such may not be 100% accurate to the real representation, but builds the screenshot based on the information available on the page.” (html2canvas documentation)

That distinction matters for visual regression tests. You can make repeated runs deterministic for the inputs html2canvas sees, but browser-only effects, cross-origin frames, unsupported CSS, and differences between browser or operating-system environments can still produce different pixels.

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.

A deterministic capture you can use as a baseline

The following example waits for fonts and images, fixes geometry and scale, freezes volatile content in the cloned document, ignores known noise, and exports after the promise fulfills.

#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
async function waitForImages() {
  const images = [...document.images];
  await Promise.all(images.map(img => {
    if (img.complete) {
      return img.decode ? img.decode().catch(() => {}) : Promise.resolve();
    }
    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 element = document.querySelector('#capture');
  if (!element) throw new Error('Missing #capture element');

  const canvas = await html2canvas(element, {
    scale: 1,
    windowWidth: 1280,
    windowHeight: 720,
    width: 1280,
    height: 720,
    x: 0,
    y: 0,
    scrollX: 0,
    scrollY: 0,
    backgroundColor: '#ffffff',
    useCORS: true,
    imageTimeout: 15000,
    logging: false,
    onclone: clonedDocument => {
      clonedDocument.querySelectorAll('[data-volatile]').forEach(el => {
        el.textContent = '[frozen]';
      });
      clonedDocument.querySelectorAll('video').forEach(video => video.pause());
    },
    ignoreElements: el => el.matches('.clock, .ad, .cursor')
  });

  const blob = await new Promise((resolve, reject) => {
    canvas.toBlob(result => result ? resolve(result) : reject(new Error('toBlob returned null')), '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);
});

Set width and height only when you want a fixed capture rectangle. For a naturally sized element, omit them but keep the viewport and scroll values fixed. The option names and defaults are listed in the official configuration reference.

Freeze the geometry that controls layout

Viewport and responsive breakpoints

Media queries can change columns, line wrapping, and hidden elements. Pass the same numeric windowWidth and windowHeight on every run, and run your test in a browser context with the same outer viewport. If a fixed header or sticky control moves when the page scrolls, set both scrollX: 0 and scrollY: 0, or use the exact offsets your test specifies.

Element bounds

Capture the same selector and, when the test requires a known rectangle, provide x, y, width, and height. Avoid calculating these values from a moving page after an animation starts. Record the canvas’s width and height in diagnostics; a dimension change usually indicates a viewport, scale, or bounding-box difference rather than a color mismatch.

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

Scale and device-pixel ratio

The documented default for scale is window.devicePixelRatio. Two machines with different DPR values therefore produce different canvas dimensions even when CSS pixels match. Use scale: 1 for one CSS pixel per output pixel, or choose another fixed value and use it in every environment. Do not rely on the host machine’s DPR for a regression baseline.

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

Wait for fonts and images before cloning

Fonts

Await document.fonts.ready before calling html2canvas, and make sure the intended font files have actually loaded. A fallback font changes glyph widths, line breaks, and element heights. If your application injects a stylesheet or uses a font loader, wait for that loader as well; document.fonts.ready only helps once the relevant font faces are known to the document.

Images

An image can be “complete” while it is still decoding. The example calls decode() when available and resolves both load and error events. Set imageTimeout deliberately; the documented default is 15,000 milliseconds. A late or missing image can alter layout as well as the pixels inside the image box.

External images and CORS

useCORS defaults to false. Set it to true only when the image server sends a suitable Access-Control-Allow-Origin header. If the server cannot provide that header, route the asset through a same-origin proxy you control. Otherwise the image may be skipped or taint the canvas, preventing toDataURL or toBlob from succeeding.

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

Remove nondeterministic DOM state with onclone

html2canvas clones the document before rendering. Use onclone to alter only that clone, leaving the live page untouched. Replace timestamps, random IDs, live counters, rotating carousel panels, network placeholders, caret styles, and animation classes with fixed values. Freeze videos or replace animated media with a poster image. This is safer than mutating the production DOM just for a test.

onclone: clonedDocument => {
  clonedDocument.querySelectorAll('[data-now], [data-random]').forEach(el => {
    el.textContent = '2026-01-01T00:00:00Z';
  });
  clonedDocument.querySelectorAll('.carousel').forEach(el => {
    el.classList.add('is-test-slide-1');
  });
}

For content that should never appear in a baseline, add data-html2canvas-ignore in markup or use an ignoreElements predicate. Typical exclusions are clocks, ads, cursor indicators, chat launchers, and video overlays.

<span class="clock" data-html2canvas-ignore>12:34:56</span>

const options = {
  ignoreElements: element => element.matches('.clock, .ad, .cursor')
};

Make output and diagnostics explicit

Background and export format

The configuration reference documents backgroundColor: '#ffffff' as the default. Set the color explicitly so a future default or stylesheet change cannot alter the baseline. Use backgroundColor: null when transparency is intentional. Call toBlob or toDataURL only after the html2canvas promise resolves; exporting earlier races the renderer.

Logging and error reporting

Keep logging: true while diagnosing missing resources, then disable it in normal runs. Supply the maintained onError hook to record resource failures; html2canvas reports the error and continues rendering, so an apparently successful promise can still contain a missing image or font.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const options = {
  logging: true,
  onclone: doc => { /* freeze state */ },
  onError: error => console.error('html2canvas resource error', error)
};

Compare runs in an order that finds the cause

  1. Canvas dimensions: compare pixel width and height first. A mismatch points to scale, viewport, or bounds.
  2. Viewport and scroll: verify windowWidth, windowHeight, scrollX, and scrollY, plus the actual browser viewport.
  3. Fonts: inspect computed font-family, loaded font-face files, and text wrapping.
  4. Images: check request status, decode completion, and CORS response headers.
  5. DOM state: diff timestamps, random values, animation classes, carousel indexes, and network-populated fields in the cloned document.
  6. Environment: record browser version, operating system, device-pixel ratio, and color settings. Keep these fixed for pixel-level tests.

Common failures and precise fixes

Symptom Likely cause Fix
Different dimensions on each machine Implicit device-pixel ratio or responsive breakpoint Set a fixed scale, windowWidth, and windowHeight; verify the real viewport.
Text wraps differently Fallback or late-loading web font Await document.fonts.ready, verify font requests, and use the same browser/font files.
Images are blank or missing Late decode, timeout, or cross-origin restrictions Await load/decode, set imageTimeout, enable useCORS only with valid headers, or use a same-origin proxy.
toDataURL or toBlob throws a security error Tainted canvas from an external image Serve the image with the required CORS header or proxy it through your origin.
A clock, ad, or cursor changes pixels Intentionally volatile content is included Mark it data-html2canvas-ignore, filter it with ignoreElements, or replace it in onclone.
Capture differs after scrolling Sticky/fixed elements and nonzero scroll offsets Set stable scrollX/scrollY and capture from a fixed viewport state.
Cross-origin iframe is empty Browser same-origin policy blocks its contentDocument Render the frame from its own origin or use a native browser screenshot service; html2canvas cannot bypass this boundary.

Performance and reliability trade-offs

  • Waiting costs time: font readiness, image decoding, and a deliberate image timeout reduce race conditions but increase capture latency. Apply the waits once per page state rather than before every small element.
  • Fixed scale costs memory: higher scales multiply canvas pixels. Use the smallest fixed scale that meets your comparison requirement.
  • Full-page captures are heavier: long pages require more layout and canvas memory. Capture a stable component when the test does not need the entire document.
  • Deterministic data beats pixel masking: replacing a timestamp in onclone preserves layout better than blurring it after export.
  • Native screenshots have a different boundary: when exact compositor output, cross-origin frames, or browser-only effects are mandatory, use a native browser screenshot API instead of treating html2canvas as a guarantee.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request returns PNG, JPEG, WebP, or PDF, while its capture pipeline accepts consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. You can turn each cleanup step off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing result.

Use the same URL with 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)
open("shot.webp", "wb").write(r.content)

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

See the complete option list and parameter details in the ScreenshotNeo documentation. It supports fixed viewports and 12 device presets, retina scale, full-page lazy-image loading, CSS-selector element capture, dark mode, custom CSS and JavaScript, click and wait conditions, blocked requests or resource types, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, PDF controls, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Existing screenshot-API parameter names also work, easing migration.

Plans include 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

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

FAQ

Can html2canvas guarantee identical pixels?

No. It can stabilize the DOM inputs it reconstructs, but browser engines, fonts, unsupported CSS, cross-origin frames, and compositor effects can still differ. Use a native browser screenshot for compositor-level identity.

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

Should I leave useCORS enabled?

Enable it only when the image origin sends a compatible Access-Control-Allow-Origin header. Otherwise use a same-origin proxy; the option cannot override server or browser security policy.

What should a regression test store besides the image?

Store canvas dimensions, viewport and scroll values, browser and DPR, font-load status, image request results, and the frozen-state inputs. Those records identify environmental drift faster than a pixel diff alone.

Frequently Asked Questions

Can html2canvas guarantee identical pixels?

No. It can stabilize the DOM inputs it reconstructs, but browser engines, fonts, unsupported CSS, cross-origin frames, and compositor effects can still differ. Use a native browser screenshot for compositor-level identity.

Should I leave useCORS enabled?

Enable it only when the image origin sends a compatible Access-Control-Allow-Origin header. Otherwise use a same-origin proxy; the option cannot override server or browser security policy.

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

What should a regression test store besides the image?

Store canvas dimensions, viewport and scroll values, browser and DPR, font-load status, image request results, and the frozen-state inputs. Those records identify environmental drift faster than a pixel diff alone.

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.73
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.90

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.