ScreenshotNeo

BlogHTML to image & PDF

How to Capture an Element’s Full Content with html2canvas and Include It in a jsPDF

Capture an entire DOM element with html2canvas, handle cross-origin assets and canvas limits, then paginate it cleanly in jsPDF.

By the ScreenshotNeo team30 September 20269 min read

How to Capture an Element’s Full Content with html2canvas and Include It in a jsPDF

Direct answer: select the element, pass it to html2canvas, and set windowWidth and windowHeight to the element’s scrollWidth and scrollHeight. This expands the cloned rendering window so content below the viewport is included. Add the resulting canvas to jsPDF with addImage(). If the scaled image is taller than a page, slice it into page-sized sections yourself or use jsPDF’s HTML plugin with an autoPaging mode.

This approach creates a raster image of the element. It is not a native browser screenshot: html2canvas reconstructs the appearance from DOM and CSS information, so unsupported CSS, cross-origin resources, iframes and very large canvases need special handling.

1. Install the libraries

For a browser application, install both packages:

npm install html2canvas jspdf

Then import them in your application:

import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

You can also load browser bundles from your package tooling. Keep the capture in a user-triggered action when possible, because browsers may restrict downloads that happen outside a click or similar gesture.

2. Capture the complete element

Give the target a stable selector and make sure it exists before starting. The important settings are the element’s scroll dimensions and useCORS for images hosted on another origin.

const element = document.querySelector('#capture');
if (!element) {
  throw new Error('Capture element not found');
}

const canvas = await html2canvas(element, {
  windowWidth: element.scrollWidth,
  windowHeight: element.scrollHeight,
  useCORS: true
});

const imageData = canvas.toDataURL('image/png');
console.log(imageData);

windowWidth and windowHeight control the cloned rendering window. Without them, a long element can be clipped at the viewport boundary. They can also change responsive breakpoints, so inspect the result when your CSS uses media queries.

Wait for content that is loaded asynchronously before calling html2canvas. For example, wait for an image’s decode() promise or for your application’s data-loading state. html2canvas captures the current DOM; it does not wait for your framework’s requests automatically.

3. Put the canvas into a PDF

For content that fits on one page, calculate a proportional height and add the image:

A scroll-sized html2canvas render can be divided into printable page slices.
A scroll-sized html2canvas render can be divided into printable page slices.
import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

async function exportElementToPdf() {
  const element = document.querySelector('#capture');
  if (!element) throw new Error('Capture element not found');

  const canvas = await html2canvas(element, {
    windowWidth: element.scrollWidth,
    windowHeight: element.scrollHeight,
    useCORS: true,
    backgroundColor: '#ffffff'
  });

  const pdf = new jsPDF({
    orientation: 'p',
    unit: 'pt',
    format: 'a4'
  });

  const margin = 36;
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const targetWidth = pageWidth - margin * 2;
  const targetHeight = canvas.height * targetWidth / canvas.width;

  if (targetHeight <= pageHeight - margin * 2) {
    pdf.addImage(canvas, 'PNG', margin, margin, targetWidth, targetHeight);
  } else {
    addCanvasSlices(pdf, canvas, targetWidth, margin);
  }

  pdf.save('capture.pdf');
}

function addCanvasSlices(pdf, canvas, targetWidth, margin) {
  const pageHeight = pdf.internal.pageSize.getHeight();
  const printableHeight = pageHeight - margin * 2;
  const scale = targetWidth / canvas.width;
  const sourceSliceHeight = Math.floor(printableHeight / scale);

  let sourceY = 0;
  let pageIndex = 0;

  while (sourceY < canvas.height) {
    if (pageIndex > 0) pdf.addPage();

    const sliceHeight = Math.min(sourceSliceHeight, canvas.height - sourceY);
    const slice = document.createElement('canvas');
    slice.width = canvas.width;
    slice.height = sliceHeight;

    const context = slice.getContext('2d');
    if (!context) throw new Error('Could not create 2D canvas context');
    context.drawImage(
      canvas,
      0, sourceY, canvas.width, sliceHeight,
      0, 0, canvas.width, sliceHeight
    );

    const renderedHeight = slice.height * scale;
    pdf.addImage(slice, 'PNG', margin, margin, targetWidth, renderedHeight);
    sourceY += sliceHeight;
    pageIndex += 1;
  }
}

exportElementToPdf().catch(console.error);

The slice routine maps a printable PDF height back to source-canvas pixels, creates a temporary canvas for each portion, and adds one portion per page. This avoids placing one image taller than the page and assuming jsPDF will slice it for you.

4. Complete HTML example

The element can be taller than its visible container. Its scroll dimensions still describe the complete content:

<button id="download">Download PDF</button>
<article id="capture">
  <h1>Quarterly report</h1>
  <p>This article continues below the viewport.</p>
  <img src="https://example.com/chart.png" alt="Revenue chart">
  <section class="long-content">...</section>
</article>
<script type="module" src="./app.js"></script>
import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';

document.querySelector('#download').addEventListener('click', async () => {
  const element = document.querySelector('#capture');
  if (!element) throw new Error('Capture element not found');

  const canvas = await html2canvas(element, {
    windowWidth: element.scrollWidth,
    windowHeight: element.scrollHeight,
    useCORS: true,
    scale: window.devicePixelRatio,
    onclone: clonedDocument => {
      clonedDocument.querySelectorAll('[data-html2canvas-ignore]').forEach(node => {
        node.remove();
      });
    }
  });

  const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
  const margin = 32;
  const width = pdf.internal.pageSize.getWidth() - margin * 2;
  const height = canvas.height * width / canvas.width;
  pdf.addImage(canvas, 'PNG', margin, margin, width, height);
  pdf.save('report.pdf');
});

Use data-html2canvas-ignore for buttons, menus or other controls that should not appear. The onclone callback changes only the cloned document used for rendering, leaving the live page unchanged.

5. html2canvas options that matter

Option Use it for Important detail
windowWidth, windowHeight Full element capture Set them to the element’s scroll dimensions when content is clipped.
scale Sharpness and output size It defaults to the device pixel ratio. Higher values consume more memory.
useCORS Images served from another origin The image server must send suitable CORS headers.
proxy Cross-origin assets without CORS Use a correctly configured proxy that fetches the resource server-side.
backgroundColor Solid or transparent background Set a color for predictable PDF output; use null when transparency is required.
imageTimeout Slow image requests Increase it when legitimate images arrive slowly, or set it to zero to disable the timeout.
ignoreElements Skip nodes programmatically Return true for controls or decorations you do not want rendered.
onclone Prepare the cloned page Hide elements, adjust styles or expand state without mutating the live page.
scrollX, scrollY Control captured scroll position Useful when fixed or sticky elements depend on page scroll.
foreignObjectRendering Try browser foreign-object rendering Support varies; inspect output instead of assuming better CSS fidelity.

The html2canvas configuration reference documents these controls: configuration options. The FAQ explains why a canvas can be empty or cut off and why browser canvas limits vary: html2canvas FAQ.

6. Pagination choices in jsPDF

Canvas slices

Slicing gives exact control over image dimensions and preserves the rendered pixels. It is predictable for dashboards, charts and mixed layouts, but a very large source canvas can consume substantial memory. You also need to decide where page boundaries fall; a slice can cut through a paragraph or table row.

HTML plugin auto-paging

jsPDF can render an element or HTML string through its HTML plugin:

const pdf = new jsPDF({ unit: 'pt', format: 'a4' });
pdf.html(document.querySelector('#capture'), {
  margin: [36, 36, 36, 36],
  autoPaging: 'text',
  html2canvas: {
    useCORS: true,
    windowWidth: document.querySelector('#capture').scrollWidth
  },
  callback: instance => instance.save('capture.pdf')
});

autoPaging: 'slice' follows the rendered page area and may cut text. autoPaging: 'text' attempts to avoid cutting text blocks and works best for mostly single-column text. The HTML plugin still relies on html2canvas and has the same browser security and CSS support boundaries. See the jsPDF HTML API and addImage API.

7. Cross-origin images, fonts and iframes

By default, cross-origin images may be skipped to prevent canvas tainting. useCORS: true works only when the remote server permits the request with appropriate CORS headers. It does not bypass browser security. If you control the asset server, configure its CORS policy; otherwise fetch through a server-side proxy and provide the proxied URL to the page.

Cross-origin iframes cannot be inspected by html2canvas. Capture content inside an iframe from the iframe’s own origin, or expose an API that returns the data you need. An existing canvas that already contains cross-origin pixels may also be unreadable. Fonts should be loaded before capture; wait for document.fonts.ready when font timing affects layout.

8. Troubleshooting

Symptom Cause Fix
Blank or half-captured output Rendering window is only the viewport, or the canvas exceeds a browser limit. Set scroll-sized windowWidth/windowHeight; reduce scale or capture sections separately.
Images are missing Remote server lacks CORS headers, image failed, or timeout expired. Enable useCORS, configure the server, use a proxy, wait for images, or adjust imageTimeout.
SecurityError from toDataURL The canvas was tainted by cross-origin content. Remove the asset, serve it with CORS, or proxy it before rendering.
Text or layout differs from the page html2canvas reconstructs supported DOM/CSS rather than taking a native screenshot. Check unsupported CSS, sticky/fixed positioning, loaded fonts and media-query changes.
PDF pages are enormous Canvas pixels were mapped to PDF points without proportional scaling. Compute target width first, then derive height from the canvas aspect ratio.
Text is cut between pages Raster slicing has no semantic knowledge of paragraphs. Use explicit break markers, render sections separately, or try autoPaging: 'text'.
Download does nothing Unhandled promise rejection or browser download policy. Wrap capture in try/catch, log the error, and start it from a user action.

9. Performance, reliability and cost

  • Control pixel count: a high scale improves sharpness but increases memory, encoding time and PDF size. Start with the device-pixel-ratio default, then lower it for very long documents.
  • Split large documents: browser canvas limits vary by platform. Rendering chapters or sections separately is safer than creating one enormous bitmap.
  • Reduce work before capture: hide animations, pause video, wait for fonts and images, and remove interactive controls with onclone or data-html2canvas-ignore.
  • Choose PNG or JPEG deliberately: PNG keeps text and flat graphics crisp; JPEG can produce smaller files for photographic content but introduces compression artifacts.
  • Validate output: check page count, image presence and the final file size. A successful promise does not guarantee that every remote resource rendered.
  • Client-side cost: html2canvas and jsPDF run in the user’s browser, so there is no screenshot API request charge. The tradeoff is device memory, browser limits and inconsistent CSS support.

10. Or skip the browser setup

If you need a reliable website capture rather than a DOM reconstruction inside your app, ScreenshotNeo provides a single GET request that returns PNG, JPEG, WebP or PDF. Its capture options include full-page rendering with lazy images loaded, element selection by CSS selector, custom CSS and JavaScript, device presets, retina scale, PDF paper sizes and page ranges, waits, request blocking, cookies, headers, geolocation and more. Read the ScreenshotNeo API documentation for the complete parameter list.

A capture service can clean common overlays before rendering the page.
A capture service can clean common overlays before rendering the page.
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}`);

Cookie banners, newsletter popups and chat widgets are removed before the shot. Bot checks, blank pages and failed loads are never billed, and response headers identify the page verdict and whether it was billed. An MCP server lets AI agents take screenshots with Claude, Cursor or another MCP client. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with the included 1,000 screenshots.

11. FAQ

Does html2canvas capture an element outside the viewport?

Yes, when the cloned rendering window is expanded to the element’s scroll dimensions and the browser can allocate a canvas of that size.

Can html2canvas create a vector PDF?

No. The usual workflow places a raster canvas into jsPDF. Use jsPDF’s HTML plugin when you need HTML-oriented pagination, while remembering that it still renders through browser-side mechanisms.

Why does useCORS not fix every image?

The remote server must explicitly allow the cross-origin request. The option cannot override browser security or make an uncooperative server share pixels.

What is the safest approach for a very long report?

Capture logical sections separately, add each section to page-sized PDF regions, and keep the scale low enough for the target devices’ canvas limits.

Can I exclude a download button?

Yes. Add data-html2canvas-ignore to it, use ignoreElements, or remove it in the cloned document from onclone.

Where are the official API details?

See the html2canvas documentation, jsPDF core API and jsPDF documentation home.