ScreenshotNeo

BlogHow-to

How to Preserve Image Opacity in html2canvas Downloads

Keep CSS and PNG transparency intact in html2canvas downloads with the right background, CORS, format, and troubleshooting steps.

By the ScreenshotNeo team1 October 20267 min read

Use a transparent canvas and export as PNG: set backgroundColor: null, render with html2canvas, and serialize with canvas.toDataURL('image/png'). html2canvas applies CSS opacity while painting pixels through the Canvas 2D context, so the downloaded file contains the composited result rather than a reusable CSS opacity rule.

This pattern preserves both an image’s internal alpha channel and CSS opacity, provided the image can be loaded without violating browser cross-origin rules.

Working download example

Save this as an HTML file and open it from a web server (for example, npx serve), rather than relying on a file:// URL:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Transparent html2canvas download</title>
  <script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
  <style>
    body { font-family: system-ui, sans-serif; }
    #capture {
      width: 640px;
      padding: 32px;
      background: transparent;
    }
    #capture img {
      display: block;
      width: 320px;
      opacity: 0.55;
    }
  </style>
</head>
<body>
  <section id="capture">
    <img src="/assets/logo-with-alpha.png" alt="Example">
  </section>
  <button id="download">Download PNG</button>

  <script>
    document.querySelector('#download').addEventListener('click', async () => {
      const element = document.querySelector('#capture');
      const canvas = await html2canvas(element, {
        backgroundColor: null,
        useCORS: true,
        scale: window.devicePixelRatio
      });

      const link = document.createElement('a');
      link.download = 'capture.png';
      link.href = canvas.toDataURL('image/png');
      link.click();
    });
  </script>
</body>
</html>

backgroundColor: null makes pixels outside the painted content transparent. The image’s own alpha and its CSS opacity are then composited into those pixels. useCORS: true only permits a cross-origin image when that image server sends a suitable Access-Control-Allow-Origin response.

How opacity is represented

There are two separate kinds of transparency:

Source Meaning What the export contains
Image alpha Per-pixel transparency encoded in PNG/WebP Preserved when the image loads successfully and the output format supports alpha
CSS opacity Opacity applied to the image or an ancestor Flattened into canvas pixels while html2canvas paints with globalAlpha
Canvas background Color behind the captured element null keeps it transparent; a color makes transparent areas appear filled

Because CSS opacity is baked into the pixels, changing the downloaded file’s CSS later cannot restore the original opacity. Keep the source assets if you need an editable version.

Options that affect transparent output

backgroundColor

Use null for a transparent canvas. A value such as 'white' or '#fff' intentionally flattens the surrounding area.

useCORS, allowTaint, and proxies

useCORS: true requests images with CORS. The server must return an appropriate Access-Control-Allow-Origin header. allowTaint: true does not bypass browser security and can leave the canvas unreadable for export. If you control neither origin, configure an image proxy or serve the asset from the same origin. html2canvas cannot circumvent the same-origin policy.

scale and dimensions

scale controls output resolution, not opacity. Use window.devicePixelRatio for a sharper download, or a fixed value such as 2. Large elements at high scale consume substantially more memory.

onclone

Use onclone to make export-only changes in html2canvas’s cloned document:

const canvas = await html2canvas(document.querySelector('#capture'), {
  backgroundColor: null,
  onclone: (clonedDocument) => {
    const copy = clonedDocument.querySelector('#capture');
    copy.style.boxShadow = 'none';
    copy.querySelectorAll('.export-only-hidden').forEach((node) => {
      node.style.display = 'none';
    });
  }
});

The live page remains unchanged.

Other useful capture settings

  • windowWidth and windowHeight control the virtual viewport.
  • x, y, width, and height define a capture region.
  • scrollX and scrollY control scroll offsets.
  • foreignObjectRendering uses a different browser mechanism and may change CSS fidelity; test it when ordinary rendering misses a feature.
  • removeContainer controls cleanup of the temporary cloned container.

See the html2canvas configuration reference for the complete option list.

Choose the correct output format

Use PNG whenever transparency matters:

const pngUrl = canvas.toDataURL('image/png');

JPEG has no alpha channel. Exporting to JPEG fills transparent pixels with a background color and can make a correctly rendered image look opaque around its edges. WebP may support alpha in browsers that support the chosen encoder, but PNG is the predictable default for downloads and matches the official html2canvas example.

For a Blob instead of a long data URL, use:

canvas.toBlob((blob) => {
  if (!blob) throw new Error('Canvas encoding failed');
  const url = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.download = 'capture.png';
  link.href = url;
  link.click();
  URL.revokeObjectURL(url);
}, 'image/png');

Why the browser view and download differ

  1. A background was added. The screenshot may be correct, but a non-null backgroundColor fills transparent surroundings.
  2. The image was skipped or tainted. Cross-origin headers are missing, so the image cannot be safely read back.
  3. The format flattened alpha. JPEG removes transparency.
  4. An unsupported CSS feature changed the paint. html2canvas re-creates the DOM rendering and does not implement every CSS property. Filters, blend modes, masks, and newer color features can differ from the browser.
  5. An ancestor controls opacity. Inspect computed styles on the image and every parent. A parent with opacity: 0.5 affects the entire subtree.

Troubleshooting checklist

Symptom Likely cause Fix
Transparent area is white Canvas background was set to a color Set backgroundColor: null and export PNG
toDataURL throws a security error Canvas is tainted by a cross-origin image Serve the image with CORS headers, use a same-origin proxy, or remove it
Image is missing It failed to load before capture or was rejected by CORS Wait for img.decode(), verify the URL, and configure CORS
Opacity looks too strong or too weak Opacity exists on an ancestor, overlay, or duplicated cloned node Inspect computed styles and use onclone for export-only overrides
Edges differ from the page Unsupported filter, mask, blend mode, or color feature Simplify the CSS, rasterize that asset, or use a browser capture method
Download looks pixelated Canvas scale is too low Increase scale while watching memory use
Capture is blank Element is hidden, outside the viewport, or fonts/assets are not ready Make it visible, set viewport options, await fonts and images, then capture

Wait for assets explicitly when needed:

await document.fonts.ready;
await Promise.all([...document.images].map((img) => {
  if (img.complete) return img.decode?.().catch(() => {});
  return new Promise((resolve) => {
    img.addEventListener('load', resolve, { once: true });
    img.addEventListener('error', resolve, { once: true });
  });
}));

Performance and reliability

  • Capture only the required element instead of the whole document.
  • Reduce scale for very large pages; canvas dimensions grow with both scale and area.
  • Lazy-load or remove unrelated images before capture.
  • Reuse a single download path and release Blob URLs after use.
  • Handle rejected promises and encoding failures so the UI reports a useful error.
  • Run captures after layout settles; animations and late network responses can produce inconsistent frames.
  • For repeatable output, pin the html2canvas version and control fonts, viewport, device pixel ratio, and asset URLs.

html2canvas is a client-side DOM renderer. It is useful when you need the current page state, but its CSS coverage and cross-origin behavior depend on the browser and source servers. The project’s supported-features page documents these rendering limits.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One request returns PNG, JPEG, WebP, or PDF, with options for transparent backgrounds, custom CSS and JavaScript, selectors, device presets, waits, cookies, headers, and more. Cookie and consent banners, newsletter popups, and chat widgets are removed before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing result.

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

Read the ScreenshotNeo API documentation for transparent-background and output options. Its MCP server lets Claude, Cursor, and other MCP clients call take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with the monthly free allowance.

FAQ

Does html2canvas preserve an image’s original CSS opacity as editable metadata?

No. The effective opacity is composited into canvas pixels during rendering. The downloaded file stores pixels, not the CSS rule.

Can I preserve transparency with JPEG?

No. Use PNG when transparent pixels or image alpha must survive.

Does useCORS: true bypass CORS?

No. It enables a CORS request; the image server still has to authorize the requesting origin.

Why is a transparent PNG opaque only after download?

Check the canvas background and output format first. A colored background or JPEG export commonly creates that result.

When should I use a server-side screenshot API?

Use one when you need consistent browser execution, remote pages, automated waits, or capture without shipping browser setup to every client. ScreenshotNeo is the direct option when you also want consent cleanup and billing that excludes failed captures.