ScreenshotNeo

BlogHow-to

Convert HTML to PNG with a Transparent Background

Convert HTML to a transparent PNG with Puppeteer, Playwright, or html2canvas. Includes runnable code, sizing, cross-origin fixes, troubleshooting, and an API option.

By the ScreenshotNeo team1 October 20267 min read

Use a real browser screenshot when pixel accuracy matters. Set the page and its wrappers to a transparent background, choose PNG, and capture with Puppeteer or Playwright using omitBackground: true. PNG preserves the alpha channel; JPEG does not. If you already run code in a browser and only need a DOM-based image, html2canvas is convenient, but it can differ from the browser’s actual pixels and is restricted by cross-origin rules.

Choose the right approach

Approach Best for Important limitation
Puppeteer Server-side rendering with maximum browser control You manage a browser runtime and binaries
Playwright Teams already using Playwright for tests or automation You still manage browser execution and readiness
html2canvas Capturing an element inside an existing browser page Reconstructs pixels from the DOM; cross-origin assets can taint the canvas
Hosted API Production services that do not want to operate browsers Usage cost, vendor dependency, privacy and quota review

Transparent PNG with Puppeteer

Puppeteer’s screenshot options document omitBackground as hiding the default white background and allowing transparency. Make every relevant layer transparent before capture; a transparent browser viewport cannot remove an opaque background declared by your own CSS.

Complete runnable example

npm install puppeteer
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1200, height: 800, deviceScaleFactor: 2 });

    await page.setContent(`
      <!doctype html>
      <html>
        <head>
          <style>
            html, body { margin: 0; background: transparent; }
            body { font-family: Arial, sans-serif; }
            .card {
              display: inline-block;
              padding: 32px;
              border-radius: 20px;
              background: linear-gradient(135deg, #6d5dfc, #29c6a6);
              color: white;
              box-shadow: 0 18px 50px rgba(0, 0, 0, .2);
            }
          </style>
        </head>
        <body>
          <div class="card" id="card">Transparent HTML card</div>
        </body>
      </html>`, { waitUntil: 'networkidle0' });

    await page.evaluate(() => document.fonts.ready);
    await page.screenshot({
      path: 'output.png',
      type: 'png',
      omitBackground: true,
      // Use fullPage: true for the complete document instead.
      clip: await page.$eval('#card', el => {
        const r = el.getBoundingClientRect();
        return { x: r.x, y: r.y, width: r.width, height: r.height };
      })
    });
  } finally {
    await browser.close();
  }
})();

The element screenshot above crops to #card. For a viewport screenshot, remove clip. For the entire scrollable document, use fullPage: true; full-page images can become unexpectedly tall and large.

Capture an element reliably

const element = await page.$('#card');
if (!element) throw new Error('Missing #card');
await element.screenshot({
  path: 'card.png',
  type: 'png',
  omitBackground: true
});

Element screenshots avoid unrelated page content. Confirm that the element itself and its ancestors do not paint an opaque background.

Transparent PNG with Playwright

Playwright exposes the same core workflow. Its screenshot API supports omitBackground, fullPage, PNG output, and element screenshots. The transparency option applies to PNG; it is not applicable to JPEG.

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

(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({
      viewport: { width: 1200, height: 800 },
      deviceScaleFactor: 2
    });
    await page.setContent(`
      <style>
        html, body { margin: 0; background: transparent; }
        #logo { padding: 24px; color: #fff; background: #111827; border-radius: 16px; }
      </style>
      <div id="logo">Transparent logo panel</div>
    `, { waitUntil: 'networkidle' });
    await page.evaluate(() => document.fonts.ready);
    await page.locator('#logo').screenshot({
      path: 'playwright-output.png',
      type: 'png',
      omitBackground: true
    });
  } finally {
    await browser.close();
  }
})();

Use await page.screenshot({ path: 'page.png', type: 'png', omitBackground: true, fullPage: true }) when the required output is the whole document.

Make the page actually transparent

  1. Set html and body to background: transparent.
  2. Remove opaque backgrounds from wrappers, root app containers, and pseudo-elements.
  3. Use PNG. JPEG has no alpha channel.
  4. Wait for fonts, images, and dynamic content before the screenshot.
  5. Open the resulting PNG over both a light and a dark background. A checkerboard preview alone is not proof that the source pixels are transparent.
html, body, #root {
  margin: 0;
  background: transparent !important;
}

A shadow, gradient, or colored component can remain visible while the surrounding pixels are transparent. That is expected: transparency applies to pixels not painted by those layers.

html2canvas: capture inside an existing browser page

html2canvas runs in the browser and builds an image from DOM information. It does not take an actual browser screenshot, so unsupported CSS or browser-specific rendering can differ from what a user sees. It is not suitable for Node.js by itself.

<script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>
<button id="save">Save PNG</button>
<div id="capture" style="background: transparent; padding: 24px;">
  Capture this element
</div>
<script>
  document.querySelector('#save').addEventListener('click', async () => {
    const canvas = await html2canvas(document.querySelector('#capture'), {
      backgroundColor: null,
      scale: window.devicePixelRatio,
      useCORS: true
    });
    const link = document.createElement('a');
    link.download = 'capture.png';
    link.href = canvas.toDataURL('image/png');
    link.click();
  });
</script>

backgroundColor: null requests transparency in the generated canvas. useCORS: true only helps when the image server sends suitable CORS headers; it cannot bypass browser security.

html2canvas cross-origin constraints

  • Images from another origin can taint the canvas unless they are served with compatible CORS headers or loaded through a configured proxy.
  • Cross-origin iframes cannot be rendered because their contentDocument is inaccessible.
  • Audit fonts, images, SVGs, canvas elements, filters, and newer CSS when output differs from the page.
  • Add data-html2canvas-ignore to controls or overlays that should not be included.

Control size, scale, and capture scope

Requirement Setting Why it matters
Repeatable dimensions Fixed viewport width and height Responsive breakpoints otherwise change layout
Sharper output deviceScaleFactor: 2 or a deliberate html2canvas scale More pixels per CSS pixel; larger files and memory use
One component Element screenshot or a measured clip rectangle Prevents unrelated page pixels from entering the asset
Whole page fullPage: true Captures the scrollable document; height can be extreme
Dynamic page Wait for a selector, fonts, images, or an explicit readiness signal A screenshot taken too early contains blanks or fallback fonts
await page.evaluate(async () => {
  await document.fonts.ready;
  await Promise.all([...document.images].map(img => {
    if (img.complete) return Promise.resolve();
    return new Promise(resolve => {
      img.addEventListener('load', resolve, { once: true });
      img.addEventListener('error', resolve, { once: true });
    });
  }));
});

Troubleshooting

Symptom Cause Fix
White background html, body, or a root wrapper paints white Set each relevant layer to transparent and use omitBackground: true.
Output is opaque despite the option The source element has an opaque background Inspect computed styles and remove the background from the element or ancestor.
JPEG has no transparency JPEG has no alpha channel Write PNG instead.
Missing images Capture happened before image load, or the URL failed Wait for image completion and check network access.
Wrong font or layout shift Web fonts were still loading Await document.fonts.ready and use a fixed viewport.
Full-page image is huge The document is very tall or device scale is high Capture an element or viewport, reduce scale, or split the document.
html2canvas throws a security error A cross-origin image tainted the canvas Enable CORS on the asset server or use a same-origin/proxy path.
Iframe is blank in html2canvas It is cross-origin Capture the iframe’s own page separately or use a server-side browser with access to the URL.
Puppeteer or Playwright cannot launch Browser binaries or system dependencies are missing Install the package’s browsers/dependencies and verify the runtime user can launch them.

Performance, reliability, and cost

  • Browser startup: Reuse a browser process and create isolated pages for multiple captures. Closing and launching a browser for every image adds avoidable latency.
  • Memory: Full-page and high-device-scale screenshots allocate large bitmaps. Limit concurrency and capture only the required element when possible.
  • Determinism: Fix viewport, timezone, locale, fonts, animations, and network readiness. Disable animations in a capture stylesheet when motion causes inconsistent frames.
  • Self-hosting: Puppeteer and Playwright have no per-shot vendor fee, but you operate browser binaries, sandboxing, updates, queues, and failed-load handling.
  • Hosted services: A hosted API removes browser operations and can provide consistent request handling, but review its current price, quotas, privacy terms, and reliability commitments before production use.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. It can return PNG, JPEG, WebP, or PDF from one GET request, with transparent backgrounds supported through its capture options. See the ScreenshotNeo API documentation for the full parameter 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)
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}`);

For a transparent PNG, add the documented transparent-background and PNG parameters to the request. ScreenshotNeo can also wait for selectors or network idle, load lazy images, capture one CSS-selected element, set a viewport and retina scale, apply custom CSS and JavaScript, and hide selectors. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients call screenshot tools directly. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Can I make a transparent screenshot with CSS alone?

CSS controls what the browser paints, but the capture API must preserve alpha. Use transparent page backgrounds, PNG output, and omitBackground: true in Puppeteer or Playwright.

Is html2canvas a true screenshot?

No. It reconstructs an image from DOM information. It is useful for in-page capture, but browser screenshots are safer when exact rendered pixels matter.

Why is my element transparent but its shadow visible?

Transparency removes unpainted pixels. A CSS shadow is painted pixels, so it remains visible by design.

Should I use full-page capture for a reusable asset?

Usually no. Capture the component or selector that will be reused. Full-page mode is intended for document screenshots and can create very tall files.

Can a transparent PNG contain a transparent iframe?

A server-side browser can capture reachable content, but html2canvas cannot render a cross-origin iframe because browser security prevents access to its document.