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.
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
windowWidthandwindowHeightcontrol the virtual viewport.x,y,width, andheightdefine a capture region.scrollXandscrollYcontrol scroll offsets.foreignObjectRenderinguses a different browser mechanism and may change CSS fidelity; test it when ordinary rendering misses a feature.removeContainercontrols 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
- A background was added. The screenshot may be correct, but a non-null
backgroundColorfills transparent surroundings. - The image was skipped or tainted. Cross-origin headers are missing, so the image cannot be safely read back.
- The format flattened alpha. JPEG removes transparency.
- 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.
- An ancestor controls opacity. Inspect computed styles on the image and every parent. A parent with
opacity: 0.5affects 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
scalefor 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.


