DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PC×
Skip to content
html2canvas

How to Prevent Duplicated Data on html2canvas and jsPDF Page Breaks

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.

Find the first stage that contains the duplicate. Check the source DOM, then inspect the html2canvas output, and only then debug jsPDF page placement. If the DOM has one block but the canvas has two, investigate the cloned document, capture dimensions, and canvas limits. If the canvas has one copy but the PDF repeats a strip at a page boundary, inspect offsets, scale conversion, page-height math, and the loop that calls addPage(). There is no universal one-line fix because repetition can be introduced at different stages.

A three-stage diagnosis

Browser HTML-to-PDF exports normally pass through three representations:

  1. DOM: your application’s live markup and state.
  2. Canvas: html2canvas clones and renders the selected element into a bitmap.
  3. PDF pages: jsPDF places that bitmap, or page-sized portions of it, on one or more pages.

Save an artifact at each boundary. Do not change CSS, capture options, and PDF pagination in the same experiment; otherwise you cannot tell which change removed the symptom.

1. Verify the DOM before exporting

Inspect the exact element passed to html2canvas in DevTools. Search for the text of a repeated row or section, and inspect application state that may render the same collection twice. A duplicate in the DOM is not a page-break bug: fix the template, data fetch, keyed list, or conditional rendering first.

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.
const target = document.querySelector('#invoice');
console.assert(target, 'Export target is missing');
console.log('rows in DOM:', target.querySelectorAll('.line-item').length);
console.log(target.innerHTML);

Also check that you are not selecting a parent containing both the original component and an off-screen export copy. A hidden duplicate may still be cloned for capture unless you exclude it.

2. Inspect the canvas independently

html2canvas accepts a DOM element and resolves a Promise with a canvas; it is a browser library, not a server-side Node.js renderer. Render once and download the canvas before involving jsPDF.

html2canvas(document.querySelector('#invoice'), {
  useCORS: true,
  logging: true
}).then(canvas => {
  console.log({ width: canvas.width, height: canvas.height });
  const link = document.createElement('a');
  link.download = 'debug-capture.png';
  link.href = canvas.toDataURL('image/png');
  link.click();
});

If the downloaded PNG already contains repeated rows, stay in the capture stage. Review the selected element, cloned-document mutations, and any export-only content. If it contains one copy, stop changing html2canvas and move to PDF placement.

3. Inspect PDF page placement

When the canvas is correct but the PDF repeats a band, log every value used by your pagination loop: source Y offset, rendered image height, page height, scale, and page number. A typical long-canvas loop must advance the source region after each addPage(); accidentally reusing the first offset places the same strip on every page.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const pageWidth = 210;       // millimetres, A4 example
const pageHeight = 297;
const imgWidth = pageWidth;
const imgHeight = canvas.height * imgWidth / canvas.width;
let remaining = imgHeight;
let sourceOffset = 0;

doc.addImage(canvas, 'PNG', 0, -sourceOffset, imgWidth, imgHeight);
remaining -= pageHeight;
while (remaining > 0) {
  sourceOffset += pageHeight;
  doc.addPage();
  doc.addImage(canvas, 'PNG', 0, -sourceOffset, imgWidth, imgHeight);
  remaining -= pageHeight;
}

This is a diagnostic pattern, not a guarantee for every unit, margin, or rotation setup. Confirm that the sign, units, and increment match your own coordinate system. Add a temporary page label or colored ruler to the source image so you can see whether each page advances.

Capture-stage problems in html2canvas

Clipping and browser canvas limits

The html2canvas FAQ warns that browser canvas size limits can produce an empty or partially cut-off canvas. Compare the capture dimensions with the element’s scroll dimensions:

const el = document.querySelector('#invoice');
console.log({
  client: [el.clientWidth, el.clientHeight],
  scroll: [el.scrollWidth, el.scrollHeight]
});
html2canvas(el, {
  windowWidth: el.scrollWidth,
  windowHeight: el.scrollHeight
}).then(canvas => {
  console.log('canvas:', canvas.width, canvas.height);
});

Matching windowWidth and windowHeight to scroll dimensions is a clipping diagnostic. It is not documented as a universal remedy for duplicated content. Very large documents may exceed a browser’s maximum canvas area; split the work into page-sized regions or reduce scale after confirming that a limit is the cause.

Cloned DOM and export-only elements

html2canvas renders a cloned document. Use onclone to make a controlled, temporary change in that clone, such as removing a fixed toolbar or forcing a print class. The live page is not changed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
html2canvas(document.querySelector('#invoice'), {
  onclone: clonedDoc => {
    clonedDoc.querySelectorAll('.sticky-toolbar, .live-chat')
      .forEach(node => node.remove());
    clonedDoc.documentElement.classList.add('pdf-export');
  }
});

For elements that should never be captured, add data-html2canvas-ignore in your markup:

<button data-html2canvas-ignore>Edit</button>

Use these controls to remove overlays and accidental export copies, not to conceal a real data duplication problem.

Selection, layout, and timing checks

  • Pass the smallest stable container that includes the intended content, not a page shell containing multiple instances.
  • Wait until asynchronous rows, images, and fonts have settled before capture. Capture after your application’s loading state is false.
  • Freeze animations and transitions in an export class; a moving transform can make adjacent frames look duplicated.
  • Check fixed or sticky descendants. Their cloned positions can overlap normal-flow content when the viewport is resized.

PDF-placement problems in jsPDF

Scale and unit conversion

jsPDF page coordinates may be millimetres, points, or another configured unit, while canvas dimensions are pixels. Compute the displayed image height from the chosen width and preserve the aspect ratio. Do not mix the canvas pixel height directly with a PDF page height.

const pdf = new jsPDF({ unit: 'mm', format: 'a4' });
const margin = 10;
const usableWidth = 210 - margin * 2;
const usableHeight = 297 - margin * 2;
const renderedHeight = canvas.height * usableWidth / canvas.width;
console.table({ usableWidth, usableHeight, renderedHeight });

Account for top and bottom margins, headers, and footers in the same coordinate system. A wrong usable height can make the next page start inside the previous slice, which looks like duplicated content.

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

Page-loop invariants

For each page, assert that the source region changes and that the loop terminates. Log values before and after addPage(). If you crop the canvas into separate images, verify that crop rectangles do not overlap by one page height or by an off-by-one pixel conversion.

let page = 1;
let y = 0;
while (y < renderedHeight) {
  console.log({ page, sourceY: y });
  // add the region represented by sourceY here
  y += usableHeight;
  if (y < renderedHeight) pdf.addPage();
  page++;
}

When a single long image is shifted upward with a negative Y value, the image itself is reused intentionally; only its visible window should change. If your implementation instead adds a cropped image, do not also apply the long-image offset.

What the jsPDF issue does—and does not—show

A reported jsPDF issue describes content breaking at a multipage transition in an html2canvas workflow. It is an individual report, not proof of a general jsPDF defect or a universal resolution. Reproduce the smallest case with a fixed DOM and a saved canvas before filing or upgrading; that evidence identifies whether the issue is in your placement code or the library combination.

Page breaks when using html2pdf.js

html2pdf.js adds its own cloning, resizing, and page-break decisions around html2canvas and jsPDF. Its documented controls can honor CSS break-before, break-after, and break-inside, plus explicit before, after, and avoid selector lists.

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

Make break policy explicit

const options = {
  margin: [12, 12, 12, 12],
  pagebreak: {
    mode: ['css'],
    before: '.chapter-start',
    after: '.chapter-end',
    avoid: ['.line-item', '.totals']
  },
  html2canvas: { scale: 2 },
  jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(options).from(document.querySelector('#invoice')).save();

Use one policy while diagnosing. If avoid-all, CSS rules, and selector rules are enabled together, the engine may move blocks in ways that differ from the live layout. Temporarily disable extra modes, then add rules back one at a time.

Check cloning and root resizing

The html2pdf.js project documents cloning behavior and root-element resizing/reflow as known implementation concerns. Compare the cloned layout with the live layout in a minimal reproduction. A root width change can alter line wrapping and element heights; a moved break can then resemble a duplicate when the same heading appears at the bottom of one page and at the top of the next.

A controlled debugging worksheet

  1. Record browser, library versions, page format, unit, scale, viewport, and the exact target selector.
  2. Save the DOM or a minimal HTML fixture and count the supposedly repeated nodes.
  3. Export the html2canvas PNG alone. Mark rows with sequential numbers.
  4. Compare canvas width and height with scrollWidth and scrollHeight; test matching windowWidth/windowHeight if clipping is suspected.
  5. Disable overlays and export copies with onclone or data-html2canvas-ignore.
  6. Generate the PDF with one page, then add pagination. Log source offsets and rendered dimensions for every page.
  7. If using html2pdf.js, test CSS breaks alone, explicit selectors alone, and then the combined configuration.
  8. Change one factor at a time and keep the canvas and DOM fixed for comparison.

Common symptoms and targeted fixes

Symptom First place to look Targeted check
Two copies appear in the downloaded PNG DOM or cloned document Count nodes; inspect onclone, fixed overlays, and ignored selectors.
PNG is cut off, blank, or ends midway Canvas capture Compare scroll dimensions, browser canvas limits, viewport, and scale.
PNG is correct; every PDF page repeats the same strip jsPDF loop Verify source offset changes after each addPage() and units are converted.
Heading appears at a page bottom and again at the next top Break engine/layout Inspect CSS breaks, selector rules, cloning, and root resizing.
Only one browser or document fails Environment or content Reduce to a fixture; test fonts, cross-origin images, sticky elements, and very large dimensions.

Performance, reliability, and architecture choices

A single long canvas is simple to inspect but can hit browser bitmap limits and consume substantial memory. Page-sized captures reduce bitmap size and can isolate failures, but they require deterministic page boundaries and may repeat setup work. Choose based on measured dimensions and layout stability, not on a blanket rule.

Keep capture and placement separate in production code: one function returns a canvas, another validates dimensions, and a third places pages. Cache the canvas only while the source DOM and styles remain unchanged. For repeatable output, wait for fonts and images, disable animations, set a fixed viewport, and record the options with the generated file.

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

ScreenshotNeo provides a website screenshot API and MCP server when you need a clean page image without maintaining a browser capture pipeline. A GET request returns PNG, JPEG, WebP, or PDF; the API accepts options for full-page lazy-image loading, element selectors, device and viewport settings, retina scale, PDF margins and page ranges, custom CSS or JavaScript, waits, request blocking, cookies and headers, timezone, geolocation, transparency, resizing, caching, signed links, asynchronous jobs, bulk capture, and usage reporting.

Use the documented endpoint and parameters at ScreenshotNeo’s 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}`);

Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan. Create a free ScreenshotNeo account to try it.

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

FAQ

Frequently Asked Questions

Can I prove whether duplication is caused by jsPDF without changing my app?

Yes. Save one html2canvas PNG from a fixed DOM, then generate a PDF from that unchanged canvas. A clean PNG with a repeated PDF isolates the placement stage.

Should I always set html2canvas windowWidth and windowHeight?

Set them to the target’s scroll dimensions when investigating clipping or incomplete captures. They are diagnostic settings, not a guaranteed duplicate-content fix.

Is html2pdf.js required for CSS page-break rules?

No. It is useful when you want its documented CSS and selector-based break controls, but a custom html2canvas plus jsPDF pipeline can implement its own pagination.

When is a separate page-sized capture preferable?

Consider it when one long canvas approaches browser limits or when each page needs independent layout. It adds boundary logic and should be validated against your document’s flow.

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

The Bottom Line

Locate the first duplicate in the DOM, canvas, or PDF placement layer. Fix that layer, then retest with fixed dimensions and one changed variable at a time.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.