ScreenshotNeo

BlogHow-to

Convert an HTML Element to JPG

Render a selected DOM element as a JPEG in the browser with html2canvas, then export it safely with canvas.toBlob().

By the ScreenshotNeo team29 September 20269 min read

Convert an HTML Element to JPG

To convert an HTML element to JPG, select its DOM node, render it to a canvas, then export that canvas as image/jpeg. In a browser, html2canvas can render an element; the native canvas.toBlob() API creates a JPEG Blob you can download or upload. Choose a quality value such as 0.95 to favor visual quality. For a shorter alternative, html-to-image provides a toJpeg() helper.

1. Install and prepare the element

This technique runs in a browser because it needs window, document, computed styles, and canvas APIs. Install html2canvas in a project built with a JavaScript bundler:

npm install html2canvas

Create a stable target selector and ensure the content has finished rendering before capture. For example:

<article id="capture">
  <h1>Quarterly report</h1>
  <p>Revenue increased this quarter.</p>
</article>
<button id="download">Download JPG</button>

In an application framework, use a ref to the rendered node rather than querying for an element before it exists. If fonts, images, or asynchronous content are still loading, wait for them before taking the capture; otherwise the output may omit them or show a fallback font.

2. Render the element and download a JPG

This complete browser example renders the element, converts it to JPEG, creates a temporary download link, and releases the object URL after the browser has had time to start the download:

The browser renders the selected node to a canvas, then the canvas encoder creates a JPEG Blob.
The browser renders the selected node to a canvas, then the canvas encoder creates a JPEG Blob.
import html2canvas from 'html2canvas';

async function downloadElementAsJpg(selector, filename = 'element.jpg') {
  const element = document.querySelector(selector);
  if (!element) {
    throw new Error(`No element found for selector: ${selector}`);
  }

  const canvas = await html2canvas(element, {
    backgroundColor: '#ffffff',
    scale: 2,
    useCORS: true,
  });

  const blob = await new Promise((resolve, reject) => {
    canvas.toBlob((result) => {
      if (result) resolve(result);
      else reject(new Error('Browser could not encode this canvas as JPEG'));
    }, 'image/jpeg', 0.95);
  });

  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  link.remove();

  // Delay cleanup so the browser can begin reading the download URL.
  setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}

document.querySelector('#download').addEventListener('click', () => {
  downloadElementAsJpg('#capture').catch(console.error);
});

Include this in a module loaded by your page or adapt the function to your build system. The example uses scale: 2 for extra pixel density; use 1 to reduce the output dimensions and memory use. The white background matters because JPEG has no transparency: transparent areas are flattened onto the chosen background color.

3. Configure size, quality, and appearance

html2canvas accepts options for the rendering stage; JPEG quality belongs to the later encoding stage. These are separate decisions. See the html2canvas configuration reference for the full option list.

Need Setting or approach What to watch
Sharper output scale: 2 or another modest multiplier Pixel dimensions and memory grow with scale; a scale of 2 produces roughly four times the pixel area.
Specific output size width, height, and optionally x, y These control canvas bounds and crop position; they do not automatically redesign responsive content.
White JPEG background backgroundColor: '#fff' JPEG cannot preserve transparent pixels.
Transparent intermediate backgroundColor: null Useful before exporting PNG; JPEG encoding still needs a solid background.
Remote images useCORS: true The image host must send suitable CORS headers. The option cannot bypass browser security.
Full element dimensions Set windowWidth and windowHeight to scroll dimensions Large canvases can exceed browser or device limits.
Exclude content Add data-html2canvas-ignore to unwanted nodes Useful for buttons, controls, or decorations within the target.
Repeated captures in a long-lived app Consider clearImageCache: true The configuration docs caution against clearing a shared cache during concurrent captures.

To control JPEG compression, pass a quality number from 0 to 1 to toBlob(). Higher values generally retain more image detail and can produce larger files; the encoder decides the exact result. The quality value does not increase the canvas resolution. For crisp text, first choose sufficient dimensions, then adjust compression based on the file-size requirement.

Wait for images and fonts

A capture taken immediately after a route change may precede image or font loading. A simple page-level wait can help when the document is still loading:

await document.fonts.ready;
await Promise.all(
  [...document.images].map((image) => {
    if (image.complete) return Promise.resolve();
    return new Promise((resolve) => {
      image.addEventListener('load', resolve, { once: true });
      image.addEventListener('error', resolve, { once: true });
    });
  })
);
await downloadElementAsJpg('#capture');

For applications that lazy-load images as they approach the viewport, scrolling the target into view can trigger loading; wait for that content to settle before calling html2canvas. This wait is an example integration pattern, not a guarantee that every site-specific loader has completed.

4. Alternative: use html-to-image

If you prefer a direct JPEG helper, html-to-image documents toJpeg(node, { quality }). Its API returns a data URL in the documented download pattern:

import * as htmlToImage from 'html-to-image';

async function downloadWithHtmlToImage() {
  const node = document.querySelector('#capture');
  if (!node) throw new Error('Target #capture was not found');

  const dataUrl = await htmlToImage.toJpeg(node, {
    quality: 0.95,
    backgroundColor: '#ffffff',
  });

  const link = document.createElement('a');
  link.download = 'element.jpg';
  link.href = dataUrl;
  link.click();
}

downloadWithHtmlToImage().catch(console.error);

Both approaches reconstruct the element from DOM and style information rather than asking the browser to capture its exact displayed pixels. Choose based on the CSS used by your target, the project’s package needs, and whether you want a Blob workflow or a data URL helper. Neither option has a universally established performance advantage in the cited documentation, so validate your actual page instead of assuming one is faster or more accurate.

5. Crop a region or send the JPG elsewhere

If you need only part of the element, pass a crop origin and dimensions to html2canvas. Coordinates are relative to the rendered element’s capture space:

const canvas = await html2canvas(element, {
  x: 40,
  y: 20,
  width: 600,
  height: 400,
  scale: 1,
});

For an upload instead of a download, reuse the Blob and send it in a multipart request to your own endpoint. This code assumes your server accepts a file field named image:

const form = new FormData();
form.append('image', blob, 'element.jpg');

const response = await fetch('/api/images', {
  method: 'POST',
  body: form,
});
if (!response.ok) {
  throw new Error(`Upload failed: ${response.status}`);
}

Do not set the multipart Content-Type header manually in browser fetch; the browser adds the boundary. For server-side processing, upload the Blob or its bytes to your backend. html2canvas itself cannot run directly in Node.js because Node does not provide the browser DOM and rendering APIs it needs.

6. Troubleshooting

Symptom Likely cause Fix
JPEG is blank or partly cut off The canvas is too large for the browser/device, or the element extends beyond the chosen viewport. Capture a smaller region, reduce scale, and use windowWidth/windowHeight matching the element’s scroll dimensions for a full-element capture. Limits vary by platform.
Remote images are missing Cross-origin image policy prevents the renderer from reading the image, or the server does not allow CORS. Use useCORS: true only when the remote host sends the necessary response headers; otherwise serve the image through a same-origin proxy you control.
SecurityError from toBlob() A cross-origin image or a previously tainted canvas contaminated the rendering surface. Resolve the image’s CORS access or remove that content. allowTaint does not make a tainted canvas exportable; it can leave the canvas unreadable.
Shadows, gradients, fonts, or layout differ html2canvas implements a subset of CSS and reconstructs the DOM rather than capturing actual rendered pixels. Check its supported CSS list, simplify or adjust unsupported styling for the capture, and compare the result in the browsers you support.
Text uses the wrong font The web font was not ready when capture began, or the font resource could not load. Await document.fonts.ready, confirm font loading completes, then capture.
Target selector is null The capture ran before the component mounted or the selector does not match. Call after render, check selector spelling, and use a framework ref where appropriate.
JPEG looks soft The output canvas has too few pixels for its display size or JPEG compression is too strong. Increase scale modestly and raise quality toward 1; inspect file size and device memory as you tune.
JPEG unexpectedly has a white background JPEG does not support transparency. Set the desired solid backgroundColor; use PNG if alpha transparency is required.
Works locally but not with a cross-origin iframe The browser prevents access to another origin’s frame document. Capture content you own from within its origin or use a permitted screenshot approach; html2canvas cannot override same-origin policy.

The html2canvas documentation describes CSS support and origin restrictions; its FAQ covers cross-origin images, canvas size limits, and why the library does not run in Node.js. MDN also documents canvas.toBlob() and the security implications of cross-origin images in canvas.

7. Performance, reliability, and cost

In-browser capture does not make a network screenshot service request, but it does consume the user’s browser CPU and memory. Large elements and high scale multiply pixel work: doubling both width and height means four times as many output pixels. Keep captures scoped to the element and resolution you need, reuse loaded assets where appropriate, and avoid running many large captures concurrently. Export with toBlob() rather than constructing a huge base64 data URL when you need a file or upload; a Blob is a natural fit for those workflows.

Reliability depends on consistent rendering conditions. Wait for dynamic content, fonts, and images; test CSS-heavy elements; handle image-load failures; and catch both rendering and encoding errors. A canvas can be blank or partial when browser limits are reached, sometimes without a useful exception, so inspect dimensions and output in the target browser/device. There is no universal canvas maximum to safely hard-code.

The DIY library path has no ScreenshotNeo API charge, though it uses client resources and requires maintenance of your browser code and asset access. If you need server-driven captures, a real browser automation setup or screenshot API adds infrastructure or service cost. Compare requirements: an element-only export from your own page favors client-side code; capturing arbitrary URLs, handling consent overlays, or supporting a backend job may call for a service.

8. Or skip the browser setup

If you need a rendered website screenshot without wiring up a browser capture flow, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns an image or PDF for a URL. It captures pages rather than selecting an arbitrary DOM node inside your current app, so use the browser method above when you specifically need that exact element.

Consent overlays and popups can change what appears in a website capture.
Consent overlays and popups can change what appears in a website capture.

See the ScreenshotNeo API documentation. Example 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,
)
r.raise_for_status()
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 request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use tools to take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free and capture your first 1,000 screenshots a month without a card.

9. FAQ

Can I convert an element to JPG without a library?

Canvas can export pixels, but the browser does not provide a general API that paints an arbitrary DOM subtree directly to canvas. A library such as html2canvas or html-to-image performs the DOM-to-image rendering step.

Can html2canvas capture a whole website?

It can render a document or a selected element in the browser, subject to CSS support, same-origin rules, and canvas dimensions. It does not provide a server-side URL screenshot from Node.js.

Should I use JPG or PNG?

Use JPG for photographic or complex imagery when a solid background is acceptable. Use PNG when you need transparency or pixel-sharp edges and text without lossy compression artifacts.

Can I capture an element from another domain?

Not by reaching across the browser’s origin boundary. A cross-origin iframe’s document is inaccessible, and cross-origin images require server CORS permission or a proxy arrangement.

Why use a Blob instead of a data URL?

A Blob integrates directly with downloads and multipart uploads without keeping a base64 string in memory. A data URL can be convenient for a small immediate preview, but it is a string representation rather than a file object.