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

Render the page once with html2canvas, scale that canvas to your PDF’s printable width, then place the same image on successive jsPDF pages with a progressively negative y offset. That produces a reliable, screenshot-style multi-page PDF in the browser. Use section-by-section rendering instead when headings, cards, or table rows must never be split.

The three layers: render, scale, paginate

1. Render the DOM

html2canvas(element, options) walks the DOM and builds a canvas representation from the properties it understands. It is not a raw browser screenshot: unsupported CSS, unloaded fonts, blocked images, filters, and complex effects can differ from the live page. The html2canvas project describes the result as a screenshot of a webpage or part of it created in the user’s browser.

2. Scale to the PDF page

Choose a PDF format and unit first. In the example below, jsPDF uses A4 millimetres. Subtract margins from the page width, then scale the canvas proportionally so its full width fits. Its resulting height is the height of the complete web page in PDF units.

3. Paginate the scaled image

jsPDF’s addImage accepts a canvas or image data and explicit x, y, width, and height coordinates. Put the image at the margin on page one. For page two and later, add a page and move the same image upward by one printable page height. The visible page window then shows the next vertical slice.

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

Prepare the HTML for capture

Give the export area a stable selector and make its visual state deterministic. Wait for asynchronous content before capturing, set a background color, and ensure the element has the full height you intend to export.

<main id="print-area">
  <h1>Quarterly report</h1>
  <section class="card">...content...</section>
</main>
<button id="download-pdf">Download PDF</button>

Images should be same-origin or served with appropriate CORS headers. A cross-origin iframe cannot be read through its contentDocument, so its contents cannot be rendered by html2canvas in the parent page.

Continuous multi-page export with jsPDF

Install the packages with your package manager, import them, and attach the function to a button. This complete pattern calculates page count from the scaled PDF height, not from raw canvas pixels.

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

async function downloadPdf() {
  const element = document.querySelector('#print-area');
  if (!element) throw new Error('Cannot find #print-area');

  // Capture after your application has loaded fonts, images, and data.
  const canvas = await html2canvas(element, {
    scale: Math.min(window.devicePixelRatio || 1, 2),
    useCORS: true,
    backgroundColor: '#ffffff'
  });

  const pdf = new jsPDF({ orientation: 'p', unit: 'mm', format: 'a4' });
  const margin = 10;
  const pageWidth = pdf.internal.pageSize.getWidth();
  const pageHeight = pdf.internal.pageSize.getHeight();
  const printableWidth = pageWidth - margin * 2;
  const scaledHeight = canvas.height * printableWidth / canvas.width;
  const printableHeight = pageHeight - margin * 2;
  const pageCount = Math.ceil(scaledHeight / printableHeight);
  const image = canvas.toDataURL('image/jpeg', 0.92);

  for (let page = 0; page < pageCount; page += 1) {
    if (page > 0) pdf.addPage();
    const y = margin - page * printableHeight;
    pdf.addImage(image, 'JPEG', margin, y, printableWidth, scaledHeight);
  }

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

document.querySelector('#download-pdf').addEventListener('click', downloadPdf);

Why the negative offset works

Suppose the scaled image is 500 mm tall and the printable region is 277 mm. Page one draws it at y = 10, showing the top 277 mm. Page two draws it at y = 10 - 277, aligning the next slice with the page’s top margin. The loop repeats until the complete image is visible.

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

JPEG, PNG, and quality

The example uses JPEG at quality 0.92, which generally keeps a photographic or screenshot-style document compact. Use canvas.toDataURL('image/png') and 'PNG' when sharp text, transparency, or flat graphics matter more than file size. A larger html2canvas scale produces more pixels but consumes more memory; cap it on very long pages.

Do not place important text exactly on a page boundary. Anti-aliasing can create a faint seam where two slices meet. A small overlap can hide that seam, but it may duplicate a thin strip. For documents where that trade-off is unacceptable, use page-aware sections.

Prevent cards and headings from being cut

Render one planned section per PDF page

Wrap each page-sized block in a class such as .pdf-page, render each block separately, and add one image to each jsPDF page. This gives you exact boundaries and makes repeating headers straightforward, but your HTML must already be page-aware.

async function addSection(pdf, section, margin = 10) {
  const canvas = await html2canvas(section, {
    scale: Math.min(window.devicePixelRatio || 1, 2),
    useCORS: true,
    backgroundColor: '#fff'
  });
  const width = pdf.internal.pageSize.getWidth() - margin * 2;
  const height = canvas.height * width / canvas.width;
  pdf.addImage(canvas, 'PNG', margin, margin, width, height);
}

async function downloadPlannedPdf() {
  const sections = [...document.querySelectorAll('.pdf-page')];
  const pdf = new jsPDF({ unit: 'mm', format: 'a4' });
  for (let i = 0; i < sections.length; i += 1) {
    if (i > 0) pdf.addPage();
    await addSection(pdf, sections[i]);
  }
  pdf.save('planned.pdf');
}

Measure each section against the printable height. If a section is taller than one page, split it deliberately rather than allowing a browser bitmap to decide where it breaks.

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

Use html2pdf.js for CSS page-break rules

html2pdf.js is a client-side wrapper around html2canvas and jsPDF. Its page-break configuration supports CSS rules, legacy markers, and explicit before, after, and avoid selectors.

html2pdf()
  .set({
    margin: 10,
    filename: 'website.pdf',
    image: { type: 'jpeg', quality: 0.95 },
    html2canvas: { scale: 2, useCORS: true },
    jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
    pagebreak: {
      mode: ['css', 'legacy'],
      before: '.start-new-page',
      avoid: ['img', '.card']
    }
  })
  .from(document.querySelector('#print-area'))
  .save();

The wrapper’s image setting supports JPEG, PNG, and WebP where the canvas implementation supports them; its quality value applies to JPEG and WebP. Page-break avoidance is a request, not a guarantee: an element taller than the available page cannot remain intact without being split or redesigned.

Choosing an approach

Approach Page-boundary control Implementation effort Visual fidelity Text/searchability Headers and footers
Direct jsPDF offset loop Low; continuous slices Lowest Screenshot-like, limited by html2canvas Bitmap; text is not selectable Draw after every addPage()
Section-by-section jsPDF High when HTML is page-aware Medium Good for planned sections Bitmap; text is not selectable Easy to include per section
html2pdf.js Medium to high through selectors and CSS Low Same html2canvas limitations Bitmap output Use page-sized sections or application code
PDF layout/server renderer Highest for semantic flow Highest Depends on the renderer Can preserve real text Supports document-level layout features

Choose the offset loop for a faithful continuous capture, section rendering when a card or heading must stay together, and a PDF layout engine when selectable text, accessibility, repeating table headers, or precise widow/orphan control is a requirement. Those capabilities are outside html2canvas and jsPDF’s bitmap workflow.

Headers, footers, and page numbers

addImage only places the captured image; repetition is your code. In the continuous loop, draw a header or footer after each image placement so it remains above the bitmap:

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.
for (let page = 0; page < pageCount; page += 1) {
  if (page > 0) pdf.addPage();
  pdf.addImage(image, 'JPEG', margin, margin - page * printableHeight,
               printableWidth, scaledHeight);
  pdf.setFontSize(9);
  pdf.text(`Page ${page + 1} of ${pageCount}`, pageWidth - margin, pageHeight - 5, {
    align: 'right'
  });
}

A captured header inside the original canvas will scroll with the page image. To repeat it independently, reserve space in your PDF layout or render page-sized sections containing that header.

Troubleshooting common failures

Blank or missing images

  • Cause: a cross-origin image taints the canvas or lacks permission for browser reuse.
  • Fix: host the asset on the same origin, configure the asset server’s CORS headers, wait for the image to finish loading, and keep useCORS: true. Do not expect useCORS to override a server that sends no permission.

Cross-origin iframe content is absent

  • Cause: browser same-origin policy blocks access to the iframe’s document.
  • Fix: render content you control in the parent page, proxy it through a permitted origin, or generate the PDF in a renderer that can load the source separately. The parent-page html2canvas call cannot read an unrelated origin’s iframe.

CSS does not match the live page

  • Cause: html2canvas reproduces only CSS properties it understands, or capture happened before fonts, images, or dynamic data were ready.
  • Fix: simplify unsupported effects, set an explicit background, await document.fonts.ready where available, wait for images, and capture after layout has settled.

The PDF has unexpected extra pages

  • Cause: page count was calculated from raw pixels, or the canvas is taller than expected.
  • Fix: calculate scaledHeight after width scaling, then use Math.ceil(scaledHeight / printableHeight). Inspect canvas.width, canvas.height, and the element’s computed dimensions.

Headings or cards split across pages

  • Cause: the offset loop knows nothing about semantic boundaries.
  • Fix: render planned sections separately, or configure html2pdf.js with avoid and explicit before selectors. Redesign elements taller than one page.

Memory errors on very long pages

  • Cause: a high-resolution canvas stores every pixel of the entire document.
  • Fix: lower scale, capture sections independently, reduce unnecessary page width, and release references after saving. There is no universal maximum canvas size; browser and device limits vary.

Performance, reliability, and output limits

Capture only the required element instead of the entire document. Keep the scale no higher than the resolution your PDF needs, and avoid duplicating a huge data URL in application state. Wait for network-loaded images and fonts before starting. A continuous canvas is simplest but makes one very tall bitmap; section rendering reduces peak size and gives recovery points if one section fails.

Client-side generation runs in the visitor’s browser, so results vary with available memory, browser security policy, fonts, and asset servers. There are no dependable speed, file-size, or browser-percentage figures to promise for every page. Test representative pages, especially those containing large images, charts, sticky elements, web fonts, or third-party embeds.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo returns a website screenshot or PDF from one GET request, so your server or script does not need to configure a browser. Its capture pipeline accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

For a direct PDF or image workflow, see the ScreenshotNeo documentation. The same endpoint supports full-page capture with lazy images, CSS-selector element capture, device and viewport settings, retina scale, custom CSS and JavaScript, clicks, waits, blocked requests, cookies, headers, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed links, asynchronous webhooks, bulk capture, usage data, and PDF paper, margin, orientation, and page-range options. It also provides an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

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

Every plan includes the features above. The Free plan includes 1,000 shots 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 to make your first captures.

Frequently Asked Questions

Can jsPDF create selectable text from html2canvas?

No. This workflow places a raster canvas in the PDF, so text behaves like pixels. Use a PDF layout engine or server-side renderer when selectable text or accessibility is required.

Why is my second page blank?

Check that the image is placed with a negative offset based on the printable page height, that page count uses the scaled height, and that the source canvas is not blank because of blocked assets.

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

Should I use PNG or JPEG?

Use JPEG for smaller screenshot-style files and PNG for sharp flat graphics or transparency. The choice does not change html2canvas’s CSS or cross-origin limitations.

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.