ScreenshotNeo

BlogHow-to

HTML to Image Converter Online

Convert HTML and CSS to PNG online with html2canvas, browser automation, or an API. Compare fidelity, CORS limits, code, options, and costs.

By the ScreenshotNeo team1 October 20269 min read

To convert HTML to an image online, choose between three approaches:

  • Browser-side DOM reconstruction: use html2canvas when the page is already open in a user’s browser and the HTML can remain on that device.
  • Real-browser automation: use Playwright when JavaScript behavior and pixel fidelity to a browser matter.
  • Hosted rendering API: use an API when you need URL screenshots, repeatable backend jobs, webhooks, bulk work, or rendering outside the user’s browser.

The quickest free implementation is html2canvas. It reconstructs the selected DOM in a canvas; it does not take a native browser screenshot. For public URLs and production workflows, a server-side screenshot API is usually easier to operate.

Which HTML-to-image method should you use?

Requirement Best fit Reason
Capture a div in the current page html2canvas No server is required and the DOM is already available.
Keep private markup on the user’s device html2canvas Rendering runs in the browser.
Capture a public URL from a backend Screenshot API or Playwright The renderer can load the URL independently.
Match browser layout and JavaScript behavior Playwright A real browser executes scripts and applies browser layout.
Run scheduled, bulk, or webhook-driven jobs Screenshot API Hosted controls remove browser lifecycle and queue management.

html2canvas’s own documentation explains that its output is based on the DOM and “may not be 100% accurate” because it builds an image from information available on the page rather than taking an actual screenshot. See the html2canvas documentation.

Convert a div to PNG with html2canvas

1. Install the library

npm install html2canvas

Then import it in a module:

import html2canvas from 'html2canvas';

You can also load the browser build from the project’s documented CDN options. The getting-started guide documents the Promise-based API and supported evergreen browsers.

2. Add a capture target and a button

<article id="invoice" class="invoice">
  <h1>Invoice #1042</h1>
  <p>Due 30 April 2026</p>
  <div class="total">$240.00</div>
  <button data-html2canvas-ignore id="download">Download PNG</button>
</article>

<script type="module">
  import html2canvas from 'html2canvas';

  const button = document.querySelector('#download');
  const target = document.querySelector('#invoice');

  button.addEventListener('click', async () => {
    const canvas = await html2canvas(target, {
      backgroundColor: '#ffffff',
      scale: window.devicePixelRatio,
      useCORS: true
    });

    const link = document.createElement('a');
    link.download = 'invoice-1042.png';
    link.href = canvas.toDataURL('image/png');
    link.click();
  });
</script>

The data-html2canvas-ignore attribute excludes the button from the rendered output. PNG export uses the canvas method documented in the project’s configuration reference.

3. Capture a selected area

const canvas = await html2canvas(document.querySelector('#invoice'), {
  x: 20,
  y: 20,
  width: 800,
  height: 500,
  scale: 2
});

x, y, width, and height crop the capture. A larger scale produces more pixels and a sharper image, but also consumes more memory.

Useful html2canvas options

Option Use Practical note
backgroundColor Set the canvas background Use null for transparency when the page supports it.
scale Increase output resolution window.devicePixelRatio is a common default; cap it for large captures.
useCORS Attempt CORS-enabled image loading The image server must send suitable CORS headers.
allowTaint Allow cross-origin images to draw A tainted canvas cannot be exported with toDataURL(); this does not bypass browser security.
onclone Modify the cloned document before rendering Hide animations, add print styles, or replace volatile content.
ignoreElements Skip elements programmatically Useful for controls that should not appear in the image.
windowWidth, windowHeight Control layout dimensions Set these when responsive CSS must render at a fixed viewport.
const canvas = await html2canvas(target, {
  backgroundColor: null,
  scale: Math.min(window.devicePixelRatio, 2),
  useCORS: true,
  ignoreElements: element => element.matches('.no-export'),
  onclone: clonedDocument => {
    clonedDocument.querySelectorAll('.animated').forEach(element => {
      element.style.animation = 'none';
      element.style.transition = 'none';
    });
  }
});

HTML to image with a complete standalone page

Save this file as index.html, install html2canvas, and serve the directory over HTTP. Opening the file directly with a file:// URL can introduce font and image restrictions.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>HTML to image</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; background: #eef2ff; }
    #card { width: 720px; padding: 3rem; border-radius: 24px; color: white;
      background: linear-gradient(135deg, #312e81, #7c3aed); }
    #card h1 { margin-top: 0; font-size: 3rem; }
    [data-html2canvas-ignore] { margin-top: 1rem; }
  </style>
</head>
<body>
  <section id="card">
    <p>Product update</p>
    <h1>HTML rendered as an image</h1>
    <p>This card is converted from live HTML and CSS.</p>
    <button data-html2canvas-ignore id="save">Save PNG</button>
  </section>
  <script type="module">
    import html2canvas from 'html2canvas';
    document.querySelector('#save').addEventListener('click', async () => {
      const canvas = await html2canvas(document.querySelector('#card'), {
        scale: Math.min(window.devicePixelRatio, 2),
        backgroundColor: null
      });
      const link = document.createElement('a');
      link.download = 'card.png';
      link.href = canvas.toDataURL('image/png');
      link.click();
    });
  </script>
</body>
</html>

Cross-origin images, fonts, and iframes

Browser security is the main limitation of client-side conversion.

  • An image hosted on another origin can taint the canvas. After that, exporting with toDataURL() or toBlob() can fail.
  • useCORS: true only helps when the remote server permits the request with CORS headers. It cannot override the browser’s content policy.
  • Cross-origin iframes cannot be read or reconstructed because your page cannot access the iframe document.
  • Web fonts must finish loading before capture. Await document.fonts.ready when font timing matters.
  • Animations, video frames, and canvas content can differ between captures. Freeze them or capture at a controlled point.
await document.fonts.ready;
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const canvas = await html2canvas(target, { useCORS: true });

When you need a native screenshot of a page you do not control, the html2canvas FAQ recommends browser capture APIs or headless tools such as Puppeteer and Playwright.

Capture a full page with Playwright

Playwright launches a real browser, waits for the page, and can produce a full-page image. Its official screenshot documentation covers these APIs.

npm install -D playwright
import { chromium } from 'playwright';

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', fullPage: true });
await browser.close();

Use a locator when you only need one element:

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

For pages with lazy-loaded images, scroll through the document before the final screenshot or wait for a known selector that indicates the content is ready. Set an explicit timeout and close the browser in a finally block in a long-running service.

HTML-to-image APIs for backend workflows

A hosted API is useful when the input is a public URL, HTML conversion runs from a queue, or you need consistent output without shipping a browser to every worker. Typical controls include viewport width and height, full-page mode, CSS injection, selector capture, delayed capture, waiting for a selector, DPI, webhooks, and PNG or PDF output. Validate the provider’s documented limits before accepting untrusted dimensions or URLs.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. Host the HTML at a reachable URL, then make one GET request. See the ScreenshotNeo API documentation for the full option list.

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 data = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', data));

ScreenshotNeo can capture full pages with lazy images loaded, one element by CSS selector, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click actions, selector waits, delays, network-idle waits, blocked ads or resource types, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, a chosen cache TTL, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage data, and PDF output.

Cookie banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and whether the request was billed. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Yearly billing gives two months free, and every feature is included on every plan. Create a free ScreenshotNeo account.

Choosing PNG, JPEG, WebP, or PDF

  • PNG: best for text, diagrams, transparency, and lossless UI captures.
  • JPEG: smaller for photographic content, but it discards detail and does not preserve transparency.
  • WebP: a good general-purpose web format when your consumers support it.
  • PDF: use when the output is a document meant for printing or pagination rather than a single raster image.

Performance and reliability checklist

  1. Capture only the required element when a full page is unnecessary.
  2. Keep viewport dimensions and scale bounded; very large canvases can exhaust browser memory.
  3. Wait for fonts, images, and application data explicitly instead of relying on an arbitrary delay.
  4. Disable animations and blinking cursors before capture.
  5. Use retries with exponential backoff for navigation and transient network errors.
  6. Record the URL, viewport, output format, renderer version, and failure reason for reproducibility.
  7. For API jobs, use caching with a deliberate TTL and inspect billing or verdict headers.
  8. For queues, make jobs idempotent so a retry does not create duplicate records.

Troubleshooting

Symptom Cause Fix
SecurityError: Tainted canvases may not be exported A cross-origin image was drawn without permitted CORS. Serve the asset with CORS headers, proxy it from your origin, or use a server-side browser/API.
Images are missing They have not loaded, are lazy-loaded, or are blocked. Wait for image completion, scroll lazy content into view, and inspect the network response.
Fonts fall back Web fonts were still loading. Await document.fonts.ready and verify the font request succeeds.
An iframe is blank It is cross-origin. Capture the iframe’s URL separately if permitted, or use a renderer with access to the required page.
Output is blurry The canvas has too few device pixels. Increase scale or the API’s device scale, while watching memory use.
Output is clipped The element or viewport dimensions are smaller than the content. Use full-page capture, explicit dimensions, or capture the target element directly.
Playwright times out The page never reaches the selected readiness condition. Wait for a specific selector, use a bounded timeout, and investigate stalled requests.
API returns an unexpected blank or bot page The destination requires a challenge or failed to load. Inspect response verdict headers, provide required headers or cookies, and do not treat a challenge page as valid content.

Security and privacy considerations

Do not place API keys in browser JavaScript or public HTML. Keep screenshot credentials on your server and restrict which URLs users can submit to prevent internal-network access. Treat captured images as potentially sensitive, especially when pages contain account data, tokens, or personal information. Redact or hide sensitive selectors before rendering and set retention rules for stored images.

Cost planning

Client-side html2canvas has no rendering-service charge, but your application pays in browser CPU, memory, bundle size, and support effort. Playwright adds browser downloads, worker capacity, startup time, and maintenance. A hosted API converts those operational costs into request pricing; compare output format, cache behavior, failed-job billing, concurrency, and wait controls rather than comparing only a per-image number.

FAQ

Can I convert raw HTML text without publishing a URL?

Yes. html2canvas can render DOM that your application creates locally. A URL screenshot API generally expects a reachable page; serve the HTML first or use an API endpoint designed for raw HTML.

Does html2canvas create a true browser screenshot?

No. It reconstructs the DOM in a canvas, so unsupported CSS and browser-only content can differ from a native screenshot.

Can I capture a cross-origin iframe with JavaScript?

Not from the parent page. Browser same-origin rules prevent access to a cross-origin iframe document.

What is the best format for text-heavy screenshots?

PNG is usually the safest choice because it preserves sharp edges and lossless detail. WebP can reduce size when supported by your delivery path.

When should I use full-page capture?

Use it for documents, reports, and long landing pages. For cards, invoices, and components, element capture is faster and avoids unrelated page content.