Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Measure the rendered element with getBoundingClientRect(), convert its pixel geometry to the jsPDF document’s unit, then pass the resulting coordinates and dimensions to doc.addImage(). The essential pattern is:

const rect = element.getBoundingClientRect();
doc.addImage(imageData, "PNG", x, y, width, height);

Use rect.width and rect.height directly only when your jsPDF coordinate system is deliberately mapped to CSS pixels. For millimetres or points, convert both position and size before placing the image.

The geometry you are transferring

A browser element and a PDF page use different coordinate systems. The browser reports a rendered rectangle in CSS pixels; jsPDF expects x, y, width and height in the base unit configured when the document was created. A reliable implementation therefore separates measurement, coordinate mapping and image placement.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What getBoundingClientRect() returns

element.getBoundingClientRect() returns a DOMRect. Its width and height describe the element’s rendered border box, including padding and borders but excluding margins. The values can be fractional. The left, top, right and bottom edges are relative to the viewport, so scrolling changes them. See the MDN reference.

CSS transforms are included in this rendered measurement. A scaled element can therefore have a different bounding-rectangle size from its layout size. If you need layout dimensions instead, use offsetWidth/offsetHeight; for content plus padding without borders, use clientWidth/clientHeight. MDN compares these choices in Determining the dimensions of elements.

What addImage() expects

The jsPDF API is doc.addImage(imageData, format, x, y, width, height, alias, compression, rotation). The coordinates and dimensions use the document’s configured unit, not an implicit browser-pixel unit. The official addImage API documents the accepted image inputs and parameters.

A complete browser example

This example measures a visible element, maps CSS pixels to millimetres, preserves the source image ratio, and places the image at a chosen PDF location. It assumes the image data is a data URL or another input accepted by your installed jsPDF version.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import { jsPDF } from "jspdf";

async function exportElementImage(element, imageData) {
  // Wait until layout and image rendering are complete.
  await new Promise(requestAnimationFrame);

  const rect = element.getBoundingClientRect();
  if (rect.width === 0 || rect.height === 0) {
    throw new Error("The element has no rendered size.");
  }

  // A4 width in millimetres, with 10 mm margins.
  const doc = new jsPDF({ unit: "mm", format: "a4" });
  const margin = 10;
  const usableWidth = doc.internal.pageSize.getWidth() - margin * 2;

  // Convert CSS pixels to millimetres at 96 CSS pixels per inch.
  // Keep this factor explicit so your print mapping is easy to change.
  const pxToMm = 25.4 / 96;
  const measuredWidthMm = rect.width * pxToMm;
  const measuredHeightMm = rect.height * pxToMm;

  // Fit to the available page width without distorting the image.
  const width = Math.min(measuredWidthMm, usableWidth);
  const height = width * (measuredHeightMm / measuredWidthMm);

  const x = margin;
  const y = margin;
  doc.addImage(imageData, "PNG", x, y, width, height);
  doc.save("element-image.pdf");
}

const element = document.querySelector("#report-chart");
const image = document.querySelector("#report-chart img");
if (!element || !image) throw new Error("Required element or image not found.");

// If you already have a data URL, pass it directly. Otherwise load one first.
exportElementImage(element, image.src);

The 96-pixel-per-inch factor is a practical CSS-to-print mapping, not a guarantee that every design should use it. If your application defines a different scale, substitute that scale consistently for both coordinates and dimensions.

Mapping positions, not just sizes

Choose the PDF origin

A DOM rectangle’s left and top are viewport coordinates. They are not automatically PDF coordinates. Most exports intentionally choose a PDF origin such as a page margin:

const x = 15; // millimetres from the PDF's left edge
const y = 20; // millimetres from the PDF's top edge

If you are reproducing an element’s position inside a measured container, subtract the container’s rectangle first, then convert the difference:

const childRect = child.getBoundingClientRect();
const containerRect = container.getBoundingClientRect();
const xPx = childRect.left - containerRect.left;
const yPx = childRect.top - containerRect.top;
const xMm = xPx * pxToMm;
const yMm = yPx * pxToMm;

This removes the viewport’s scrolling and the container’s page position from the calculation. If you deliberately need document-relative browser coordinates, add window.scrollX and window.scrollY to left and top before converting, as described by MDN.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Account for borders and padding

Because the bounding rectangle includes padding and borders, it is correct when the PDF should match the visible border box. For content-only placement, subtract the relevant border and padding values:

const styles = getComputedStyle(element);
const borderX = parseFloat(styles.borderLeftWidth) + parseFloat(styles.borderRightWidth);
const borderY = parseFloat(styles.borderTopWidth) + parseFloat(styles.borderBottomWidth);
const contentWidthPx = rect.width - borderX - parseFloat(styles.paddingLeft) - parseFloat(styles.paddingRight);
const contentHeightPx = rect.height - borderY - parseFloat(styles.paddingTop) - parseFloat(styles.paddingBottom);

Do not subtract margins from rect.width or rect.height; margins are not included in the rectangle in the first place.

Pixel, millimetre and point units

Pick the unit that matches how you design the PDF. Millimetres and points make print layouts explicit; pixels can reduce conversion work when the source is entirely browser-based. jsPDF documents configurable base units and notes that correct pixel scaling requires the px_scaling hotfix. Check the documentation for the version installed in your project at the jsPDF unit and px_scaling notes.

Using millimetres

const doc = new jsPDF({ unit: "mm", format: "a4" });
const pxToMm = 25.4 / 96;
const width = rect.width * pxToMm;
const height = rect.height * pxToMm;
doc.addImage(imageData, "PNG", 10, 10, width, height);

Using points

const doc = new jsPDF({ unit: "pt", format: "letter" });
const pxToPt = 72 / 96;
doc.addImage(imageData, "PNG", 36, 36, rect.width * pxToPt, rect.height * pxToPt);

Using pixels

const doc = new jsPDF({
  unit: "px",
  format: [800, 1100],
  hotfixes: ["px_scaling"]
});
doc.addImage(imageData, "PNG", 40, 40, rect.width, rect.height);

Do not assume that selecting unit: "px" alone establishes the mapping you want; use the documented hotfix and verify the behavior of your installed version.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Preserve the image’s aspect ratio

Supplying both width and height lets you stretch an image. If the target ratio differs from the source ratio, circles become ovals and text can look distorted. MDN explains the ratio principle in Understanding and setting aspect ratios.

When the source dimensions are known, calculate one dimension from the other:

const targetWidth = 160;
const targetHeight = targetWidth * (sourceHeight / sourceWidth);
doc.addImage(imageData, "PNG", x, y, targetWidth, targetHeight);

For a maximum width and height, use one scale factor:

const scale = Math.min(maxWidth / sourceWidth, maxHeight / sourceHeight);
const width = sourceWidth * scale;
const height = sourceHeight * scale;

Measurement choices and edge cases

Zero dimensions

If all border boxes are empty, getBoundingClientRect() returns zero dimensions. This commonly happens when the element is display:none, not mounted yet, inside a collapsed panel, or waiting for an image or font to load. Measure after the intended state is visible and reject zero values before calling addImage.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Images that have not loaded

Wait for the source image’s load event (or use await image.decode() where supported) before measuring. Otherwise the layout can change after you calculated the PDF dimensions.

Transforms and zoom

Transforms affect getBoundingClientRect(). Browser zoom and device-pixel ratio can also make visual expectations differ from CSS-pixel calculations. Decide whether the PDF should follow the transformed visual result or the untransformed layout, then choose getBoundingClientRect() or the layout dimensions accordingly.

Multi-page content

addImage does not automatically split a tall image across pages. Compare the calculated bottom edge with doc.internal.pageSize.getHeight(); add a page and reset y, or slice the source image yourself when content exceeds the available area.

Troubleshooting

The image is the wrong size

  • Confirm that the PDF unit matches your conversion factor.
  • Check whether a CSS transform changed the measured rectangle.
  • Ensure you did not mix viewport pixels with document coordinates.
  • Log rect.width, the converted width and the final addImage arguments.

The image is shifted when the page scrolls

left and top are viewport-relative. For placement relative to another DOM element, subtract the parent rectangle. For document-relative browser coordinates, add the scroll offsets before conversion.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The PDF image is distorted

Recalculate the second dimension from the source aspect ratio instead of passing unrelated width and height values. Also check that the source dimensions are the intrinsic image dimensions, not a transformed CSS size.

Nothing appears in the PDF

  • Verify that the image data is a valid data URL, canvas output or other supported input.
  • Make sure the element and image have nonzero dimensions.
  • Check that x, y, width and height are finite numbers.
  • Confirm the image is inside the page boundaries and that the selected format matches the data.

The export captures stale layout

Run measurement after the layout-changing state has rendered, typically after a frame, and wait for asynchronous images, fonts or chart rendering to finish. If a responsive breakpoint can change the element, lock the intended viewport or perform the measurement and export in the same stable state.

Performance, reliability and caching

Measure once and reuse the resulting geometry rather than calling layout-reading APIs repeatedly in a tight loop; repeated reads interleaved with style writes can force synchronous layout. For many images, collect all rectangles first, then perform PDF writes. Downsize very large source images before embedding when document size matters, and use JPEG only when its compression artifacts are acceptable.

Keep the conversion policy in one function so every element uses the same scale. Record the jsPDF version and unit configuration alongside exported documents if reproducibility matters. Finally, test at narrow and wide layouts, with scrolling, hidden sections, borders, transforms and unloaded images.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If your goal is a clean screenshot or PDF of a web page rather than a custom in-browser jsPDF composition, ScreenshotNeo provides a single website screenshot API request. It accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups and chat widgets before capture. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and each response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

One-call examples

See the parameter details in the ScreenshotNeo documentation.

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)
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}`);

All 63 options are available on every plan, including full-page lazy-image loading, CSS-selector element capture, device presets and custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting and an OpenAPI specification. Existing parameter names used by other screenshot APIs also work for easier migration.

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Create a free ScreenshotNeo account and start with the 1,000 included screenshots.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

FAQ

Frequently Asked Questions

Should I measure with getBoundingClientRect or offsetWidth?

Use getBoundingClientRect when the PDF should match the rendered, transformed border box. Use offsetWidth and offsetHeight when you need integer layout dimensions that ignore transforms.

Can jsPDF place an image automatically inside the measured element’s page position?

No. You must map the DOM origin and unit to PDF coordinates, then provide explicit x, y, width and height to addImage.

Why does scrolling change my measured x and y?

The rectangle’s positional edges are viewport-relative. Subtract a reference container or add scroll offsets, depending on whether you need local or document-relative coordinates.

Does addImage preserve aspect ratio for me?

No. It uses the width and height you supply, so calculate one dimension from the source ratio when distortion is unacceptable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.