ScreenshotRender
← Back to blog
Tutorials

JavaScript Screenshot: html2canvas, Headless, or API

Robert Belt·9 min read
Updated On :
Orange minimalist illustration of JavaScript turning a web page into an image

Every guide to taking a screenshot in JavaScript reaches for html2canvas, then goes quiet about what it can't do: cross-origin images, iframes, and half of modern CSS. By the end of this you'll know the three real ways to capture a web page in JavaScript, and a clear rule for which one to reach for.

The three are html2canvas (a client-side library that re-renders the DOM), a headless browser like Puppeteer or Playwright (a real Chromium you drive server-side), and a single HTTP call to a screenshot API (no browser at all). They run from least to most reliable for the specific job of capturing a URL, and each one fits a different situation.

What's the fastest way to take a screenshot in JavaScript?

The fastest way to take a screenshot in JavaScript is one HTTP request to a screenshot API, with no browser to install and no DOM to re-render. With ScreenshotRender the whole capture is a single fetch call: const res = await fetch("https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://news.ycombinator.com&fullPage=true"). The JSON response carries a hosted image URL at (await res.json()).data.screenshot.

If you'd rather keep everything in the browser, html2canvas is the fastest no-server option. If you need to drive the page yourself, a headless browser gives you the most control. The rest of this post shows all three, so you can pick by how much infrastructure you want to own.

How do you take a screenshot in the browser with html2canvas?

html2canvas takes a screenshot in the browser by walking the live DOM and repainting every node onto a canvas, which you then export as an image. After loading the library, one call captures the page: html2canvas(document.body).then(canvas => canvas.toDataURL("image/png")). The promise resolves with a canvas element, and toDataURL turns it into a base64 PNG you can download, upload, or display.

Because html2canvas reads computed styles and redraws them itself, it never contacts a server and never asks permission. That is its whole appeal: it runs entirely client-side, so it's the obvious first reach for a "save this page as an image" button or for capturing a chart a user just generated in front of you. It works beautifully in a quick demo. Then you point it at a real page, and the gaps show up.

How do you screenshot a single element or div with html2canvas?

Pass the element instead of the whole body: html2canvas(document.querySelector("#card")) returns a canvas sized to just that node. This is the common case for a "download this receipt" or "share this card" button, where you want one component and not the surrounding page chrome. You export it the same way, with canvas.toDataURL("image/png"). The catch is the one the next section is about: if that card holds an image served from another domain, it comes back blank.

Why does html2canvas produce blank or broken screenshots?

html2canvas produces blank or broken screenshots because it is not a real screenshot, it is a re-render, and it can only draw what it understands. It reimplements CSS layout in JavaScript, so any feature it has not built support for (and the web adds them faster than any library can keep up) is dropped or drawn wrong. Three categories cause most of the broken output.

  • Cross-origin images. An image loaded from another domain taints the canvas under the browser's security model, so the export either throws or returns blank pixels. Setting the right CORS header on the image is the only fix, and you don't control headers on third-party assets.
  • Iframes. Embedded content such as a video player, a payment field, or an ad lives in a separate document html2canvas cannot reach into, so it renders as an empty box.
  • Unsupported CSS. Newer layout, filters, and effects render differently or not at all, because the library has to reimplement each property by hand and is always chasing the browser.

On a marketing page full of third-party images and embeds, that adds up to a screenshot that looks nothing like the page. The fix is to render the page in an actual browser, which is exactly what the next two methods do.

How do you take a real screenshot with a headless browser in Node.js?

You take a real screenshot in Node.js by driving a headless browser, Puppeteer or Playwright, which loads the URL in actual Chromium and captures what it renders. Because it is a real browser, cross-origin images, iframes, and every CSS feature just work, and a full page capture is one call: await page.screenshot({ path: "page.png", fullPage: true }).

The flow is launch the browser, open a page, go to the URL, screenshot, close. Puppeteer's page.screenshot and Playwright's equivalent take the same core options, including the fullPage flag and a clip region for a single element. For the full Node walk-through, see our Puppeteer screenshot guide.

The cost is everything around that one line. You ship a Chromium binary, keep it patched, give each concurrent capture enough memory, and watch the version drift that turns a green CI run red after an unrelated upgrade. For a few screenshots on your laptop that's fine. At real volume it becomes an infrastructure project of its own.

Skip the Chromium build and the EC2 fleet.

You don't need a headless browser farm to capture a page. Send a URL to ScreenshotRender and get back a hosted PNG, rendered by a real Chromium with cookie banners and ads already stripped. 100 free screenshots a month, no credit card.

Try a render

How do you screenshot a website in JavaScript without running a browser?

You screenshot a website in JavaScript without running a browser by calling a screenshot API: send the URL to an HTTP endpoint and get a hosted image back, which from any JavaScript runtime is a single fetch call. The full request is one copy-pasteable line: const res = await fetch("https://screenshotrender.com/api/v1/screenshot?apiKey=YOUR_API_KEY&url=https://news.ycombinator.com&fullPage=true"). Read the image URL from (await res.json()).data.screenshot and store it or download the bytes.

The parameters are the whole surface. url is the page to capture, fullPage=true grabs the entire scrollable document instead of the default 1280 by 720 viewport, and wait takes a millisecond delay for pages that finish rendering after the initial load, like a React app. There is no SDK; the same call works in Python, Node.js, and cURL because anything that speaks HTTP works identically. For a long page, full page capture handles the scroll-and-stitch you would otherwise write by hand.

The other thing you stop doing yourself is cleanup. Cookie consent banners, ad overlays, and chat widgets are removed automatically before every capture, on every plan including the free one, so the image is the page rather than the page plus three popups. The free plan covers 100 screenshots a month with no credit card, which is enough to wire capture into a front end or a serverless function before you think about volume.

When does each JavaScript screenshot method fail?

Every method here fails on a short, predictable list, and knowing which case you are in saves you a day of debugging the wrong layer.

  • Client-side cross-origin and canvas taint. html2canvas goes blank the moment a third-party image or iframe is involved, and there is no client-side fix when you don't control the asset's CORS headers.
  • Headless infrastructure and CI drift. A self-run Puppeteer or Playwright works until a Chromium update or a missing font on the CI runner shifts pixels and breaks brittle tests; you own that drift. An HTTP call renders on fixed infrastructure, so the same URL stays stable across runs.
  • Login walls. A URL-only API takes a URL, not a session cookie, so it cannot reach a page behind a login. This is the one case where driving the browser yourself wins, because you can script the sign-in. For public pages, the one-line call is the simpler trade.
  • Bot protection. A vanilla headless browser gets served a challenge page on bot-protected sites, so the capture is the challenge, not the site. You need a stealth setup and usually a residential proxy, or an API that bakes it in. ScreenshotRender ships Stealth Mode on the Hobby plan and above; see our guide to screenshotting Cloudflare-protected sites for what changes.

None of this makes one method the best. A client-side library, a headless browser, and an API solve three different problems; the trick is matching the method to the page in front of you.

Common questions about taking a screenshot in JavaScript

Can you take a screenshot with pure JavaScript?

Only in a limited way. In the browser, html2canvas reads the live DOM and repaints it onto a canvas, which is close to a screenshot but cannot see cross-origin images or iframes. The browser's own Screen Capture API can grab a tab, but only after the user clicks through a permission prompt. For an automated, pixel-accurate capture of any URL, you call a screenshot API or drive a headless browser server-side.

Is html2canvas a real screenshot?

No. html2canvas does not photograph the rendered page; it walks the DOM and redraws each node onto a canvas using its own implementation of CSS. Anything it does not support, such as cross-origin images, iframes, or newer CSS features, is dropped or comes back blank. The output often looks right on a simple page and wrong on a real one.

How do I screenshot a specific element in JavaScript?

Pass the element to html2canvas instead of the whole body, for example html2canvas(document.querySelector("#card")). It returns a canvas sized to just that node, which you export with canvas.toDataURL("image/png"). The same cross-origin and iframe limits apply, so a card full of third-party images still comes back incomplete.

How do I capture a full page screenshot in JavaScript?

Client-side html2canvas struggles with long scrollable pages because it has to lay the whole document out in memory. A headless browser handles it cleanly: Puppeteer and Playwright both take full page screenshots in one call. A screenshot API does the same by adding fullPage=true to the request, with nothing to install.

Can I use the browser's Screen Capture API to screenshot a page?

The Screen Capture API (navigator.mediaDevices.getDisplayMedia) captures the screen, a window, or a browser tab as a live video stream you can grab a frame from. It always requires a user gesture and a permission prompt, so it suits user-initiated recording, not automated or server-side screenshots. For capturing a URL programmatically, it is the wrong tool.

Is there a free JavaScript screenshot API?

Yes. ScreenshotRender's free plan includes 100 screenshots per month with no credit card, full page capture, and cookie banner and ad removal enabled by default. You call it from any JavaScript runtime with a single fetch request, so there is no browser to install or maintain.

The honest takeaway: match the tool to the job. For a button that saves a simple component the user already sees, html2canvas in the browser is fine. To drive a page, sign in, or fill a form, run Puppeteer or Playwright and accept the browser that comes with it. And to turn any public URL into a clean image from a line of JavaScript, the HTTP call is the least to maintain.

Keep reading