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

Use a real browser screenshot when the PNG must match what users see. Playwright and Puppeteer render the page in Chromium (or another supported browser) and save the result directly. Use html2canvas when code running inside the page needs a canvas image and its CSS, image-origin, and iframe limitations are acceptable. The examples below show full-page and element captures, predictable dimensions, transparent backgrounds, readiness waits, troubleshooting, and a hosted alternative.

Choose the right HTML-to-PNG method

Requirement Best starting point Reason Check first
PNG should match the browser rendering Playwright or Puppeteer They capture the rendered browser page rather than rebuilding it from DOM data. Wait for content and assets; set the viewport, scope, and pixel scale.
Capture one element Playwright locator/page screenshot or Puppeteer element screenshot Both support targeted element captures. Check clipping, scroll position, and whether the element must be scrolled into view.
Run capture code in the page itself html2canvas It reconstructs a canvas from DOM information and can export a PNG data URL. Verify CSS support, external-image CORS, and iframe origins.
Predict dimensions Browser automation with an explicit viewport and scale You control CSS pixels versus device pixels. State which pixel unit your downstream system expects.

A browser screenshot is a rasterization of the page surface. html2canvas is different: it traverses the DOM and paints supported elements into a canvas. Its own documentation warns that the result may not be 100% accurate to the real representation.

Playwright: the most controllable browser capture

Install Playwright, install a browser, then navigate and save a PNG. The default screenshot type is PNG when no other type is selected.

npm install playwright
npx playwright install chromium

Capture a visible viewport

const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch();
  const page = await browser.newPage({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1
  });
  await page.goto('https://example.com', { waitUntil: 'networkidle' });
  await page.screenshot({ path: 'page.png' });
  await browser.close();
})();

waitUntil: 'networkidle' is useful for mostly static pages, but it can never finish on applications with analytics, sockets, or polling. In those cases, wait for a meaningful selector instead:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.locator('[data-ready="true"]').waitFor({ state: 'visible' });
await page.screenshot({ path: 'ready.png' });

Capture the complete scrollable page

await page.screenshot({ path: 'full-page.png', fullPage: true });

Full-page capture stitches the page’s scrollable height into one image. Lazy-loaded content may not exist until it is scrolled into view. If necessary, scroll through the page or trigger the site’s lazy-load mechanism before taking the screenshot.

Capture one element

const card = page.locator('.pricing-card').first();
await card.screenshot({ path: 'card.png' });

The locator screenshot clips to that element’s bounding box. Make sure fonts and images have finished loading; otherwise the measured box and final pixels can change.

Transparency, scale, masking, and motion

await page.screenshot({
  path: 'transparent.png',
  omitBackground: true,
  scale: 'css',
  animations: 'disabled',
  mask: [page.locator('.personal-data')]
});
  • omitBackground: true preserves transparency where the page has no painted background.
  • scale: 'css' produces one output pixel per CSS pixel. scale: 'device' uses device pixels and can create a larger PNG on high-DPI settings.
  • Disable animations and transitions for stable captures; otherwise the same page can produce different frames.
  • Mask sensitive or intentionally variable regions when visual comparison does not require their real content.

Apply capture-only CSS or hide selectors

await page.addStyleTag({ content: `
  .cookie-banner, .chat-widget { display: none !important; }
  * { animation: none !important; transition: none !important; }
` });
await page.screenshot({ path: 'clean.png', fullPage: true });

For a repeatable pipeline, define the browser engine and version, viewport, device scale, color scheme, timezone, fonts, and readiness condition. Playwright notes that host OS, browser version, settings, hardware, power source, and headless mode can all affect rendering; pixel-identical output across uncontrolled machines is not a safe promise.

Puppeteer: a straightforward Chromium workflow

Install Puppeteer, launch Chromium, choose a wait condition, and call page.screenshot().

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
npm install puppeteer
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.screenshot({ path: 'page.png', fullPage: true });
  await browser.close();
})();

Save a particular element

const element = await page.$('.pricing-card');
if (!element) throw new Error('Element not found');
await element.screenshot({ path: 'card.png' });

ElementHandle.screenshot() captures the element’s rendered bounds. If a selector matches multiple nodes, choose the intended one or iterate over all matches and assign distinct filenames.

html2canvas: capture from browser JavaScript

Load html2canvas in the page, select a DOM node, render it, and convert the canvas to a PNG data URL.

<script src="https://cdn.jsdelivr.net/npm/html2canvas@latest/dist/html2canvas.min.js"></script>
<script>
  html2canvas(document.querySelector('#invoice'), {
    useCORS: true,
    scale: window.devicePixelRatio,
    backgroundColor: null
  }).then(canvas => {
    const link = document.createElement('a');
    link.download = 'invoice.png';
    link.href = canvas.toDataURL('image/png');
    link.click();
  });
</script>

Crop a region and set output scale

const node = document.querySelector('#dashboard');
const canvas = await html2canvas(node, {
  x: 0,
  y: 0,
  width: node.scrollWidth,
  height: node.scrollHeight,
  scale: 2,
  useCORS: true
});
const pngDataUrl = canvas.toDataURL('image/png');

Know what html2canvas cannot guarantee

  • It does not take a literal screenshot of the browser surface; unsupported CSS properties may be missing or rendered differently.
  • Images must be same-origin or available through a correctly configured proxy. useCORS: true helps only when the image server sends permissive CORS headers.
  • Cross-origin iframe documents are inaccessible because of browser security rules. You cannot reconstruct their contents from the parent page.
  • Compare the PNG with the on-screen page before relying on it for invoices, visual regression, or pixel-accurate design work.

Make screenshots deterministic

Fix the geometry

Set width, height, device scale, and capture scope explicitly. Distinguish CSS pixels from device pixels in filenames, metadata, or API contracts. A 1,440 CSS-pixel viewport at device scale 2 can produce roughly twice as many output pixels in each dimension.

Wait for the actual readiness signal

  1. Navigate with domcontentloaded or a bounded network-idle wait.
  2. Wait for a selector that proves the relevant component is rendered.
  3. Wait for web fonts and images when they affect layout. In page JavaScript, await document.fonts.ready is useful for fonts.
  4. Disable animations, transitions, rotating carousels, and timestamps when comparing images.

Control environment and privacy

Pin browser versions in CI, install the same fonts, use a fixed timezone and locale, and avoid machine-dependent rendering settings. Never include real customer data in test screenshots; mask or replace it before writing files to shared storage.

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

Troubleshooting HTML-to-PNG failures

The PNG is blank or only partly rendered

  • Cause: capture ran before the app mounted or before lazy content loaded. Fix: wait for a stable selector, scroll lazy sections, and increase the navigation timeout only when the page genuinely needs it.
  • Cause: the page is blocked by a bot check or requires authentication. Fix: provide an authenticated browser context and permitted headers/cookies, or capture an accessible staging URL.

Fonts, images, or layout differ between runs

  • Cause: fonts were not available, animations were mid-frame, or the host/browser differed. Fix: install and preload fonts, disable motion, pin the browser and OS image, and use a fixed viewport and scale.
  • Cause: image dimensions were unknown when the screenshot was taken. Fix: wait for image loads and reserve dimensions with HTML/CSS.

html2canvas throws a security error or omits images

The canvas becomes tainted when it draws cross-origin images without suitable CORS headers. Host the assets on the same origin, configure the image server’s CORS policy, or use a server-side browser capture. A cross-origin iframe cannot be read from the parent page.

The element screenshot is clipped

Check overflow containers, transforms, sticky headers, and scroll position. Scroll the locator into view, capture the correct ancestor when the visual region extends outside the element’s box, and test the resulting dimensions rather than assuming the CSS box equals the visible design.

Network-idle never completes

Long-lived connections and telemetry can keep the network busy forever. Replace network-idle with domcontentloaded plus a selector or application-specific readiness flag, and impose a finite timeout so failed jobs terminate cleanly.

Performance, reliability, and file handling

Launching a fresh browser for every URL is simple but expensive. For batches, keep one browser process and create isolated contexts or pages per job; close each context to release cookies and memory. Limit concurrency so CPU, RAM, and upstream rate limits remain stable. Reuse a context only when sharing authentication is intentional.

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.

Write to a temporary filename and rename after a successful screenshot, so consumers never read a partial PNG. Record URL, viewport, scale, browser version, timestamp, and readiness condition beside the file. Retry transient navigation failures with a bounded backoff, but do not blindly retry deterministic selector errors or authorization failures. Set maximum page size and navigation timeouts to protect workers from unbounded documents.

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be switched off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.

One GET request returns PNG, JPEG, WebP, or PDF. The API supports full pages with lazy images, CSS-selector elements, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click-before-capture, selector waits, network-idle or delay waits, request/resource blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, 100-URL bulk calls, usage data, and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which eases migration.

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

See the ScreenshotNeo documentation for option names and response headers. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 shots. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. Create a free ScreenshotNeo account to start.

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

FAQ

Should I use PNG or another image format?

PNG is lossless and well suited to text, UI screenshots, and transparency. Choose JPEG or WebP when smaller files matter more than lossless edges; ScreenshotNeo can return all three.

Can a screenshot include a cross-origin iframe?

A browser automation screenshot can visually include an iframe if the frame loads normally. html2canvas cannot inspect a cross-origin iframe document from the parent page.

Why is my full-page image extremely tall?

Full-page mode captures the page’s entire scrollable height. Consider an element or viewport capture, or split long output into separate sections when downstream systems impose dimension limits.

Is html2canvas suitable for server-side rendering?

It is designed to run in a browser context. For server-side jobs that must match browser pixels, use Playwright or Puppeteer, or a hosted browser screenshot API.

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.

Frequently Asked Questions

Should I use PNG or another image format?

PNG is lossless and well suited to text, UI screenshots, and transparency. Choose JPEG or WebP when smaller files matter more than lossless edges; ScreenshotNeo can return all three.

Can a screenshot include a cross-origin iframe?

A browser automation screenshot can visually include an iframe if the frame loads normally. html2canvas cannot inspect a cross-origin iframe document from the parent page.

Why is my full-page image extremely tall?

Full-page mode captures the page’s entire scrollable height. Consider an element or viewport capture, or split long output into separate sections when downstream systems impose dimension limits.

Is html2canvas suitable for server-side rendering?

It is designed to run in a browser context. For server-side jobs that must match browser pixels, use Playwright or Puppeteer, or a hosted browser screenshot API.

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

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.