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.

Wait for the data and the pixels, not an arbitrary number of milliseconds. Fetch the AJAX response, render it into the target element, mark that element ready, then wait for fonts and images before calling toPng or toJpeg. This makes the capture synchronize with application state instead of guessing when a slow request will finish.

The reliable sequence

The html-to-image package captures the DOM as it exists when its Promise-based function runs. If the request or rendering is still in progress, the output can contain a spinner, empty containers or only part of the report. Use this sequence:

  1. Set a loading state.
  2. Await the AJAX request and check its HTTP status.
  3. Render the response into the capture node.
  4. Expose a deterministic ready marker.
  5. Wait for fonts and image decoding that affect pixels.
  6. Call toPng, toJpeg or another capture function.

Browser implementation

import { toPng } from 'html-to-image';

function renderReport(data) {
  return `<h1>${escapeHtml(data.title)}</h1>
    <p>Total: ${data.total}</p>
    <img src="${escapeAttribute(data.logoUrl)}" alt="">`;
}

function escapeHtml(value) {
  return String(value).replace(/[&<>"']/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;',
    '"': '&quot;', "'": '''
  }[c]));
}
function escapeAttribute(value) { return escapeHtml(value); }

export async function captureAfterAjax() {
  const node = document.querySelector('#report');
  if (!node) throw new Error('Missing #report element');
  node.dataset.state = 'loading';

  const response = await fetch('/api/report');
  if (!response.ok) throw new Error(`Report request failed: HTTP ${response.status}`);
  const data = await response.json();

  node.innerHTML = renderReport(data);
  node.dataset.state = 'ready';

  if (document.fonts?.ready) await document.fonts.ready;
  await Promise.all([...node.querySelectorAll('img')].map(img =>
    img.decode ? img.decode().catch(() => undefined) : Promise.resolve()
  ));

  return toPng(node);
}

Call it from an event handler and use the returned data URL as an image source or download. Set the ready marker only after the final DOM mutation. A selector such as #report[data-state="ready"] is therefore a useful contract for both browser code and hosted renderers.

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

Preventing stale responses

If users can request several reports, an older response may arrive after a newer one. Use an AbortController or request identifier, and only mark the currently selected report ready. Otherwise the capture can be internally consistent but show the wrong record.

What html-to-image waits for—and what it does not

The library clones the node, copies computed styles, embeds web fonts and images, serializes HTML through SVG foreignObject, and rasterizes to a canvas for formats such as PNG. Its capture Promise represents that conversion work; it does not know that your AJAX request has finished unless your code awaits it first.

Fonts

Web fonts can change line breaks and element dimensions. document.fonts.ready waits for the document’s font-loading set before capture. If a font is optional or deliberately not loaded, capture after the fallback layout is the intended design instead.

Images

An image element can exist before its pixels are decoded. Calling img.decode() where available lets decoding finish without failing the whole capture when one optional image is broken. Verify that the final source is the one you expect before calling the library.

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.

Animations and transitions

A ready marker can appear while CSS animation is still moving an element. Disable transitions for a capture-specific class, or add a short settling delay after the marker. A delay is a visual-settling tool, not a substitute for waiting on the request.

Using a hosted renderer with a readiness selector

When the page must be rendered outside the user’s browser, expose a completion element in the page and wait for it. HTML2IMG’s JavaScript integration uses waitForSelector; raw HTTP requests use wait_for_selector. The selector should identify completed content, not merely a container that exists during loading.

Rank #2
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
await client.screenshot({
  url: 'https://app.example/reports/42',
  waitForSelector: '#report[data-state="ready"]',
  msDelay: 400,
  width: 1440,
  height: 900
});

Prefer the selector whenever you control the markup: it returns as soon as the element exists, whereas a delay always waits its full duration. Keep a bounded timeout in your job system and report a clear error if the marker never appears.

Raw request spelling

For an HTTP payload, send the snake-case option:

{
  "url": "https://app.example/reports/42",
  "wait_for_selector": "#report[data-state="ready"]",
  "ms_delay": 400
}

Do not mix the SDK’s camel-case name with the raw request name; an ignored option can result in a loading-state capture.

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

AJAX content inside an iframe

A parent-page selector cannot inspect the DOM inside an iframe. If the widget is same-origin, have the outer page observe a message or callback and set its own ready marker:

window.addEventListener('message', event => {
  if (event.origin !== 'https://widgets.example' || event.data?.type !== 'report-ready') return;
  document.querySelector('#report').dataset.state = 'ready';
});

For a cross-origin iframe that cannot cooperate, use a bounded delay after the frame is expected to render. HTML2IMG documents a 1–5000 ms delay range for this fallback. A delay can still be too short on a slow connection, so prefer an outer-page marker whenever possible.

Cross-origin resources and canvas failures

The canvas becomes tainted when an image or other resource is fetched without suitable cross-origin permission. The result can fail when the library reads canvas pixels. Serve images with appropriate CORS headers, use same-origin assets, or proxy assets through an origin you control. Test fonts, CSS background images and SVG files as well as ordinary <img> elements.

Hosted renderers also need publicly reachable HTTPS resources. Private localhost URLs, authentication-only endpoints and blocked mixed-content requests cannot be loaded by a remote browser. Keep credentials in the renderer’s server-side configuration rather than exposing API keys in page JavaScript.

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

Why a fixed sleep is fragile

setTimeout(resolve, 2000) waits two seconds whether the response completed in 100 ms or 2.5 seconds. It wastes time on fast pages and captures incomplete content on slow ones. A state marker couples the capture to the actual completion condition. Use a delay only for animation settling, iframe limitations or another condition that cannot expose state.

Large pages, limits and operational choices

Browser-side capture

  • Best when the data is already in the user’s session and no server credential should leave the browser.
  • Subject to browser memory, canvas limits and data-URI limits for very large DOM trees.
  • Cannot bypass cross-origin restrictions that the page itself does not satisfy.

Hosted capture

  • Useful for scheduled jobs, consistent viewport sizes and server-side delivery.
  • Requires public resources, careful secret management and a readiness selector or fallback delay.
  • HTML2IMG documents a 30-second server-side script execution budget; design the AJAX endpoint and readiness path to finish within that limit.

For either approach, log the URL, readiness condition, viewport, response status and failure reason. Save the HTML state or request identifier when diagnosing intermittent captures, but avoid logging sensitive response data.

Troubleshooting checklist

The image shows a spinner or empty panel

The capture started before rendering completed. Await the fetch, mutate the node, and set the ready marker only afterward. For a hosted job, correct the selector spelling and verify that the marker is actually present.

The selector wait times out

Inspect the page in the same environment as the renderer. Common causes are an AJAX error, a selector that differs between success and failure markup, a private URL, or a marker set before the request finishes. Add an error state and make the job fail rather than silently returning a loading image.

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

Fonts or line wrapping differ

Wait for document.fonts.ready, ensure the font files are reachable with CORS, and capture at the intended device pixel ratio and viewport. A late font can change both dimensions and text placement.

Images are missing

Check each image’s final URL, wait for decoding, and inspect server response headers. A 404, blocked mixed-content request or CORS failure must be fixed at the resource origin; waiting longer cannot repair it.

Canvas or security error

Look for cross-origin images, SVGs or CSS backgrounds. Make them same-origin or configure appropriate CORS headers. Very large nodes may also exceed browser data limits; capture smaller elements or split the page.

The iframe remains blank

Selector waits do not inspect iframe contents. Add a postMessage-based outer marker, or use a bounded delay within the documented 1–5000 ms range and verify the frame’s network access.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 provides a hosted screenshot API and MCP server. It can wait for a selector, delay or network idle, while also handling full-page captures, lazy images, custom CSS and JavaScript, cookies, headers, viewport and device settings. Before the capture it accepts cookie/consent banners 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.

One call returns an image or PDF:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for selector-wait parameters and the other 63 options. The same service can be used by AI agents through its MCP tools take_screenshot, get_page_info and capture_pdf.

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

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(`HTTP ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Sign up for ScreenshotNeo free.

Frequently Asked Questions

Should I wait for the network request or for a DOM selector?

Wait for the request in browser code, then expose a selector that means the DOM is complete. Hosted capture can wait on that selector.

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

Can html-to-image capture a page by URL?

No. It captures a DOM node in the current browser context; use a hosted browser API when you need URL-based rendering.

Is a longer timeout a fix for CORS errors?

No. CORS, authentication and unreachable resources must be corrected at the resource or renderer configuration level.

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.