How to Render an HTML and CSS Logo as a PNG with JavaScript
Convert a DOM logo to a transparent PNG with html2canvas, handle fonts and CORS, fix blank exports, and compare an SVG or API workflow.
To render an HTML and CSS logo as a PNG in JavaScript, pass the logo element to html2canvas(), await the returned canvas, convert it with canvas.toDataURL('image/png'), and download the data URL.
import html2canvas from 'html2canvas';
const logo = document.querySelector('#logo');
const canvas = await html2canvas(logo, {
backgroundColor: null,
scale: window.devicePixelRatio,
useCORS: true
});
const link = document.createElement('a');
link.download = 'logo.png';
link.href = canvas.toDataURL('image/png');
link.click();
This reconstructs the element from its DOM and CSS. It is not a pixel-level screenshot of the browser, so unsupported CSS, unavailable fonts, cross-origin images, and cross-origin iframes can change the result. The html2canvas documentation describes this DOM-based approach and its limits.
1. Create a logo element
Give the logo a stable selector and make its dimensions explicit. Explicit dimensions prevent a responsive layout from changing between the screen and the export.
<div id="logo" class="logo" aria-label="Acme logo">
<span class="logo-mark"></span>
<span class="logo-name">Acme</span>
</div>
<button id="download-logo" type="button">Download PNG</button>
.logo {
width: 640px;
height: 180px;
display: flex;
align-items: center;
gap: 28px;
padding: 32px;
box-sizing: border-box;
color: #102033;
background: transparent;
font: 700 64px/1.1 Inter, system-ui, sans-serif;
}
.logo-mark {
width: 116px;
height: 116px;
border-radius: 28px;
background: linear-gradient(135deg, #6d5dfc, #22c7a9);
transform: rotate(12deg);
}
.logo-name {
transform: rotate(-12deg);
}
2. Install and capture it
Install html2canvas with npm, yarn, or pnpm, then run the capture after the page and its assets have loaded.
npm install html2canvas
# or: yarn add html2canvas
# or: pnpm add html2canvas
import html2canvas from 'html2canvas';
async function downloadLogo() {
const element = document.querySelector('#logo');
if (!element) throw new Error('Missing #logo element');
// Wait for web fonts and images used by the logo.
if (document.fonts?.ready) await document.fonts.ready;
await Promise.all(
[...element.querySelectorAll('img')]
.filter((img) => !img.complete)
.map((img) => new Promise((resolve) => {
img.addEventListener('load', resolve, { once: true });
img.addEventListener('error', resolve, { once: true });
}))
);
const canvas = await html2canvas(element, {
backgroundColor: null,
scale: window.devicePixelRatio,
useCORS: true
});
const link = document.createElement('a');
link.download = 'logo.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
document.querySelector('#download-logo').addEventListener('click', downloadLogo);
toDataURL() uses PNG when you omit the MIME type. You can pass 'image/png' explicitly, as above, to make the output choice clear.
3. Preserve transparency and control resolution
backgroundColor: nullkeeps the transparent background. A color such as'#ffffff'makes that color the canvas background.scalecontrols raster density.window.devicePixelRatioproduces sharper output on high-DPI screens, while1produces a canvas at CSS-pixel density.- The output pixel dimensions are the element dimensions multiplied by the scale. A 640×180 element at scale 2 becomes a 1280×360 PNG.
const canvas = await html2canvas(document.querySelector('#logo'), {
backgroundColor: null,
scale: 2,
width: 640,
height: 180,
useCORS: true
});
The configuration reference also provides x, y, windowWidth, and windowHeight when you need a controlled crop or viewport. Use the element’s full scroll dimensions when the logo contains content that extends beyond its visible box.
4. Wait for fonts, images, and other resources
Capture only after the resources that affect the logo are ready. Otherwise the export can contain fallback fonts, empty image boxes, or a partially painted gradient.
async function waitForLogoAssets(element) {
if (document.fonts?.ready) await document.fonts.ready;
const images = [...element.querySelectorAll('img')];
await Promise.all(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 });
});
}));
}
const element = document.querySelector('#logo');
await waitForLogoAssets(element);
const canvas = await html2canvas(element, { useCORS: true });
For CSS background images, ensure the stylesheet and image request have completed before capture. If a remote font or image cannot be fetched with CORS, inline it or serve it through a same-origin proxy.
5. Handle CORS and missing images
A cross-origin image without permission taints the canvas. MDN documents that calling toDataURL(), toBlob(), or getImageData() on a tainted canvas throws a SecurityError. Set useCORS: true and configure the image server to return an appropriate Access-Control-Allow-Origin header.
<img
src="https://cdn.example.com/logo-mark.png"
crossorigin="anonymous"
alt=""
>
const canvas = await html2canvas(document.querySelector('#logo'), {
useCORS: true,
backgroundColor: null
});
try {
const png = canvas.toDataURL('image/png');
console.log(png);
} catch (error) {
if (error.name === 'SecurityError') {
console.error('A cross-origin asset tainted the canvas:', error);
}
throw error;
}
JavaScript cannot bypass browser origin rules. A same-origin proxy, an inline data URL, or an image host with the correct CORS response is required. Cross-origin iframes cannot be rendered by html2canvas.
6. Use an SVG-native path when the logo is SVG
If the logo is already an inline SVG, serializing that SVG and drawing it onto an off-screen canvas can be simpler than reconstructing HTML and CSS. External fonts and images inside the SVG still need correct loading and CORS headers.
function svgToPng(svgElement, width, height) {
const source = new XMLSerializer().serializeToString(svgElement);
const blob = new Blob([source], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.clearRect(0, 0, width, height);
context.drawImage(image, 0, 0, width, height);
URL.revokeObjectURL(url);
resolve(canvas.toDataURL('image/png'));
};
image.onerror = (error) => {
URL.revokeObjectURL(url);
reject(error);
};
image.src = url;
});
}
const pngDataUrl = await svgToPng(document.querySelector('svg'), 640, 180);
const link = document.createElement('a');
link.download = 'logo.png';
link.href = pngDataUrl;
link.click();
7. Know html2canvas’s fidelity limits
html2canvas builds a representation from the DOM. Its documentation cautions that the result may not be 100% accurate to the page’s real representation. Only CSS properties implemented by the library render correctly. Complex filters, masks, unsupported blend modes, unavailable fonts, and cross-origin iframes can differ from the browser view.
foreignObjectRendering is available in browsers that support it, but it is an option rather than a guarantee that every CSS feature will work.
| Requirement | Recommended path |
|---|---|
| HTML and CSS logo | html2canvas and a controlled, loaded DOM element |
| Inline SVG logo | Serialize SVG, draw to an off-screen canvas, export PNG |
| Pixel-accurate page screenshot | Use a browser screenshot service or browser automation instead of DOM reconstruction |
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Blank or transparent output | The selector matched nothing, the element has zero dimensions, or content was not loaded. | Check document.querySelector(), computed width and height, and wait for fonts and images. |
SecurityError during export |
A cross-origin image or background image tainted the canvas. | Return CORS headers, set useCORS: true, use crossorigin="anonymous", inline the asset, or proxy it. |
| Images are missing | The image failed, was still loading, or was not CORS-enabled. | Check the network request, wait for load, and fix the image response headers. |
| Text uses the wrong font | The web font was not ready when the clone was rendered. | Await document.fonts.ready and verify the font request succeeds. |
| CSS effects differ | The property is not implemented by html2canvas. | Simplify the effect, use an SVG asset, or capture with a real browser screenshot. |
| Logo is clipped | The element or capture dimensions are smaller than its content. | Set explicit dimensions and use width, height, or the full scroll dimensions. |
| Download does nothing | The click was not initiated by a user gesture or the data URL is too large. | Call the function from the button handler; for large images, use canvas.toBlob() and an object URL. |
9. Improve performance and reliability
- Capture only the logo element instead of the entire document.
- Keep
scaleas low as your output requirements allow; higher values increase memory and encoding time. - Reuse a stable, fixed-size component for predictable output.
- Wait for resources once, then capture; repeated captures should avoid unnecessary layout changes.
- Prefer
toBlob()for large files so you do not keep a large base64 string in memory. - Handle rejected image and font requests explicitly and provide a retry or fallback asset.
const canvas = await html2canvas(document.querySelector('#logo'), {
backgroundColor: null,
scale: 2,
useCORS: true
});
canvas.toBlob((blob) => {
if (!blob) throw new Error('PNG encoding failed');
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.download = 'logo.png';
link.href = url;
link.click();
URL.revokeObjectURL(url);
}, 'image/png');
10. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It can capture a hosted logo page as PNG, JPEG, WebP, or PDF without wiring html2canvas into your page. See the ScreenshotNeo API documentation for all 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 bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then((fs) => fs.writeFile('shot.webp', bytes));
Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result. 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 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
11. Short FAQ
Can I export a transparent PNG?
Yes. Set backgroundColor: null and ensure no ancestor or child paints an unwanted background.
Why does my logo look blurry?
Increase scale, commonly to window.devicePixelRatio or 2, and avoid enlarging a small PNG after export.
Can html2canvas capture an iframe?
It cannot render a cross-origin iframe. Move the content into the same origin or capture the hosted page with a real browser screenshot service.
Should I use PNG or SVG?
Use SVG when you need resolution independence and your source is already vector. Use PNG when a raster asset is required by a download, upload, or downstream API.
Does html2canvas make a pixel-perfect screenshot?
No. It reconstructs supported DOM and CSS. Unsupported effects, fonts, external assets, and iframes can differ from the screen.


