ScreenshotNeo

BlogHow-to

How to Take a Screenshot of an HTML Page Using JavaScript

Learn how to capture an HTML page, full document, or single element with JavaScript using Playwright, Puppeteer, html2canvas, or ScreenshotNeo.

By the ScreenshotNeo team1 October 20268 min read

To take a screenshot of an HTML page using JavaScript, choose the tool based on where your code runs:

  • Node.js or server-side automation: use Playwright or Puppeteer. They drive a real browser and capture the rendered result.
  • Code running inside the page: use html2canvas. It reads DOM nodes and styles, then reconstructs an image in the user’s browser.
  • No browser setup: use ScreenshotNeo’s screenshot API to return an image or PDF from one request.

Playwright and Puppeteer are the better choice when visual fidelity, full-page capture, or repeatable server-side output matters. html2canvas is useful for an “Export this card” button inside a web app, but it has browser security and rendering limitations.

1. Capture an HTML page with Playwright

Playwright launches a real browser, loads the page, waits for navigation to settle, and saves the rendered page as an image.

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({
  viewport: { width: 1440, height: 900 }
});

await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({
  path: 'page.png',
  fullPage: true
});

await browser.close();

Install Playwright with npm install playwright. The first browser launch may also require Playwright’s browser installation command in your deployment environment.

Playwright screenshot options

Option Use
path Writes the image to a file. Omit it to receive screenshot bytes.
fullPage: true Captures the complete scrollable document instead of only the viewport.
type: 'png', 'jpeg', or 'webp' Selects the output format when supported by the installed browser.
quality Controls JPEG or WebP quality where the format supports it.
omitBackground: true Produces transparency when the page background allows it.

Capture one HTML element with Playwright

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com/invoice', { waitUntil: 'networkidle' });

await page.locator('.invoice').screenshot({ path: 'invoice.png' });
await browser.close();

Use a locator that identifies exactly one visible component. If the selector matches multiple elements, narrow it with a class, ID, or locator filter.

Wait for dynamic content before the screenshot

await page.goto('https://example.com/dashboard', { waitUntil: 'domcontentloaded' });
await page.locator('[data-report-ready="true"]').waitFor();
await page.screenshot({ path: 'dashboard.png', fullPage: true });

A navigation event only tells you that the document loaded. Your application may still be fetching data, rendering charts, or loading images. Wait for a meaningful application selector when the page has a known ready state.

2. Capture an HTML page with Puppeteer

Puppeteer provides the same general workflow: launch Chromium, navigate, wait for the page, and call page.screenshot().

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
const page = await browser.newPage();

await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({
  path: 'page.png',
  fullPage: true
});

await browser.close();

Install it with npm install puppeteer. Puppeteer’s screenshot method can write a file or return image data when you omit the output path.

Capture one element with Puppeteer

const element = await page.$('.invoice');
if (!element) {
  throw new Error('Invoice element was not found');
}

await element.screenshot({ path: 'invoice.png' });

Return screenshot bytes instead of writing a file

const imageBytes = await page.screenshot({
  type: 'png',
  fullPage: true
});

// For example, write imageBytes to object storage or an HTTP response.

Returning bytes is useful for an API endpoint, an upload pipeline, visual regression tests, or an image-processing step.

3. Capture an element in the browser with html2canvas

html2canvas runs inside the page. It examines the selected element, reads its DOM and applied styles, and paints a canvas representation. It is convenient for same-origin cards, receipts, reports, and user-triggered exports.

<button id="save-capture">Save capture</button>
<section id="capture">
  <h2>Monthly report</h2>
  <p>Revenue increased this month.</p>
</section>

<script type="module">
  import html2canvas from 'https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/+esm';

  document.querySelector('#save-capture').addEventListener('click', async () => {
    const element = document.querySelector('#capture');
    const canvas = await html2canvas(element, {
      backgroundColor: '#fff'
    });

    const blob = await new Promise(resolve =>
      canvas.toBlob(resolve, 'image/png')
    );

    const link = document.createElement('a');
    link.download = 'capture.png';
    link.href = URL.createObjectURL(blob);
    link.click();
    URL.revokeObjectURL(link.href);
  });
</script>

This is a DOM reconstruction, not a native browser screenshot. Complex CSS, browser-rendered controls, cross-origin assets, and embedded documents may differ from what the user sees.

Capture the whole document with html2canvas

const canvas = await html2canvas(document.documentElement, {
  windowWidth: document.documentElement.scrollWidth,
  windowHeight: document.documentElement.scrollHeight,
  backgroundColor: '#fff'
});

For long pages, capturing one element or several sections is often more reliable than creating one extremely large canvas.

4. Full-page versus viewport screenshots

A viewport screenshot captures only the visible browser area. A full-page screenshot includes content below the fold.

Requirement Playwright or Puppeteer html2canvas
Visible viewport Default screenshot Capture the target element at its current size
Entire scrollable page fullPage: true Render a document-sized element or set canvas dimensions
One component Locator or element screenshot Pass the component to html2canvas()

Lazy-loaded images may not exist until the page scrolls. With Playwright or Puppeteer, wait for those images or trigger the page’s lazy-loading behavior before capturing.

5. Control viewport, device scale, and output

Set the viewport explicitly so repeated captures use the same responsive layout.

const context = await browser.newContext({
  viewport: { width: 390, height: 844 },
  deviceScaleFactor: 2,
  isMobile: true,
  hasTouch: true
});
const page = await context.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'mobile-retina.png', fullPage: true });

A larger device scale factor produces more pixels and sharper output, but increases memory use and file size. Use PNG for lossless UI screenshots, JPEG for photographic pages, and WebP when your downstream systems support it.

6. Handle fonts, images, animations, and dynamic pages

  • Fonts: wait for document.fonts.ready before capturing when text layout depends on web fonts.
  • Images: wait for important images to complete loading.
  • Animations: disable or pause animations to prevent frame-to-frame differences.
  • Data: wait for a page-specific ready selector after API requests finish.
  • Cookie banners and overlays: dismiss them or hide them before the capture if they obscure the content.
await page.goto('https://example.com/report', { waitUntil: 'networkidle' });
await page.evaluate(async () => {
  await document.fonts.ready;
  const images = Array.from(document.images);
  await Promise.all(images.map(image => {
    if (image.complete) return Promise.resolve();
    return new Promise(resolve => {
      image.addEventListener('load', resolve, { once: true });
      image.addEventListener('error', resolve, { once: true });
    });
  }));
});
await page.screenshot({ path: 'stable.png', fullPage: true });

7. Cross-origin limitations

Cross-origin assets are the main limitation of browser-only html2canvas captures. Images from another origin can taint the canvas unless the server permits the request with appropriate CORS headers. Use a same-origin proxy or configure the asset server when you control it.

Cross-origin iframes cannot be rendered by html2canvas because browser security rules prevent access to their document. A real browser automation tool can navigate to a page, but it still cannot bypass authentication or origin permissions that the page itself does not grant.

8. Troubleshooting JavaScript screenshots

Symptom Cause Fix
Screenshot is blank The capture ran before content rendered, or the page failed to load. Check the response and console, wait for a ready selector, and verify the target URL.
Only the top of the page appears The screenshot captured the viewport. Set fullPage: true in Playwright or Puppeteer.
Charts or images are missing Assets were still loading or were blocked. Wait for images, fonts, and application data; inspect network failures.
Text uses the wrong font Web fonts had not finished loading. Await document.fonts.ready before the screenshot.
html2canvas throws a security or tainted-canvas error A cross-origin image or canvas was used without CORS permission. Serve the asset with CORS headers or proxy it through the same origin.
An iframe is empty in html2canvas The iframe is cross-origin. Capture the iframe separately from an environment that can access it, or use a browser-level workflow.
Element screenshot fails The selector matched nothing, multiple elements, or a hidden element. Use a precise selector and wait for the element to be visible.
Browser fails to launch in deployment Browser binaries or system dependencies are unavailable. Install the required Playwright or Puppeteer browser package and dependencies in the image.
Captures differ between runs Animations, responsive layout, fonts, or live data changed. Fix the viewport, freeze data, disable animation, and wait for stable readiness.

9. Performance, reliability, and cost

Performance

  • Reuse a browser process for multiple pages instead of launching a new browser for every URL.
  • Keep viewport and output dimensions no larger than required.
  • Use element screenshots when a full document is unnecessary.
  • Use a deliberate readiness condition instead of an unnecessarily long fixed delay.
  • Large full-page canvases consume significant browser memory; split very long documents when possible.

Reliability

  • Set navigation and operation timeouts.
  • Record the URL, viewport, wait condition, and browser version with each capture.
  • Retry transient navigation failures with a limit and inspect failures before retrying application errors.
  • For visual regression, keep fonts, data, viewport, timezone, and browser version consistent.

Cost

Self-hosted Playwright, Puppeteer, and html2canvas are open-source libraries, but browser CPU, memory, storage, and operational work are your responsibility. A hosted screenshot API trades browser maintenance for request pricing and provider limits. Choose based on capture volume, isolation requirements, and how much browser infrastructure you want to operate.

10. Or skip the browser setup

ScreenshotNeo returns a screenshot or PDF from one GET request, so you do not need to install or maintain Playwright or Puppeteer. Its capture workflow accepts cookie and consent banners before the shot 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, and response headers identify the page verdict and billing status. ScreenshotNeo also provides an MCP server for AI agents, with tools for screenshots, page information, and PDFs.

See the ScreenshotNeo documentation for all request options.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.

11. Choosing the right approach

Need Best fit
Faithful rendered page from Node.js Playwright or Puppeteer
Full-page or element capture in automation Playwright or Puppeteer
Export one same-origin component from a web app html2canvas
Cross-origin-heavy pages without browser maintenance ScreenshotNeo
AI-agent screenshot workflows ScreenshotNeo MCP server

FAQ

Can JavaScript take a screenshot without Node.js?

Yes. html2canvas can create a canvas in the browser, but it reconstructs the page and is subject to same-origin and iframe restrictions.

What is the most accurate JavaScript screenshot method?

Playwright or Puppeteer, because they capture the page through a real browser after it renders.

How do I screenshot only one HTML element?

Use page.locator('selector').screenshot() in Playwright, an element handle in Puppeteer, or pass the element to html2canvas.

Why does my full-page screenshot miss lazy-loaded content?

The images may not have loaded yet. Trigger the page’s loading behavior and wait for the relevant images or ready selector before capturing.

Can I return screenshot data from an API endpoint?

Yes. Omit the local path option in Playwright or Puppeteer and send the returned bytes in your response or storage pipeline.