Create Multiple Page PDF with Top and Bottom Margins using jsPDF addImage

English 简体中文 繁体中文 ภาษาไทย Tiếng Việt
Summary

When generating multi-page PDFs from HTML content using html2canvas and jsPDF, a common challenge is the absence of automatic top and bottom margins at page breaks, leading to visually awkward content splits. The process typically involves rendering the entire webpage as a single canvas image, then manually slicing and positioning segments onto individual PDF pages with jsPDF.addImage(). To implement consistent margins, developers must adjust the addImage y-position for each page, accounting for desired topPadding and bottomPadding. The key technique involves using doc.setFillColor(255, 255, 255) and doc.rect() to mask the content that would otherwise overflow into the margin areas, effectively creating clean, readable padding at the top and bottom of each page. This ensures a professional document appearance without abrupt content truncation.

I’ve been working on a browser extension that saves web pages as PDFs in reader mode using jsPDF + html2canvas, providing a clean and distraction-free way to view them offline. Everything was going smoothly until I noticed a UI issue—there were no top and bottom margins at the page break area.

original.jpg

When converting an HTML page to a PDF using html2canvas and jsPDF, the process works by first rendering the entire webpage as a canvas. html2canvas captures everything visible in the browser and converts it into an image. This image is then added to a PDF using jsPDF.addImage().

However, if the webpage is longer than a single page in the PDF, jsPDF doesn’t automatically handle page breaks intelligently. Instead, it simply places the full image onto the first page, and if the image is too tall, it will get cut off. To support multi-page PDFs, we have to manually slice the image and position each segment correctly on separate pages.

By default, jsPDF does not leave any margins at page breaks—each new page simply picks up exactly where the previous one left off. This can create a visually awkward experience, especially if text is abruptly split between pages. To fix this, we need to manually calculate and add margins. This involves adjusting the positioning of the image for each page and using doc.rect() to mask the areas where we want padding.

The original code without margins would be like:

const imgHeight = fullHeight * scale; // height of the HTML content image
const pageHeight = 297; // A4 height in mm

let heightLeft = imgHeight;
let position = 0;
let currentPage = 1;

// if we have more than one page, we need to add padding to the top and bottom
if(imgHeight > pageHeight){
    // Additional pages
    while (heightLeft > 0) {
        doc.addImage(
            jpegDataUrl,
            'JPEG',
            0,                  // x position
            position,           // y position
            pdfWidth,           // width
            imgHeight,          // height: ensure last page fits correctly
            "",
            "FAST"
        );

        currentPage++;
        position -= pageHeight;  // Move the starting position down
        heightLeft -= pageHeight;
    
        if(heightLeft > 0){
            doc.addPage();
        }
    }
} else {
    // Single page
    ...
}

The new code which supports adding top and bottom margins

const imgHeight = fullHeight * scale; // height of the HTML content image
const pageHeight = 297; // A4 height in mm
const topPadding = 10;  
const bottomPadding = 10;
const contentHeight = pageHeight - topPadding - bottomPadding;

let heightLeft = imgHeight;
let position = topPadding;
let currentPage = 1;

// if we have more than one page, we need to add padding to the top and bottom
if(imgHeight > contentHeight){
    // Additional pages
    while (heightLeft > 0) {
        doc.addImage(
            jpegDataUrl,
            'JPEG',
            0,                  // x position
            position,           // y position
            pdfWidth,           // width
            imgHeight,          // height: ensure last page fits correctly
            "",
            "FAST"
        );

        doc.setFillColor(255, 255, 255);
        doc.rect(0, 0, pdfWidth, topPadding, 'F'); // padding top
        doc.rect(0, pageHeight - bottomPadding, pdfWidth, bottomPadding, 'F'); // padding bottom 

        currentPage++;
        position -= contentHeight;  // Move the starting position down
        heightLeft -= contentHeight;
    
        if(heightLeft > 0){
            doc.addPage();
        }
    }
} else {
    // Single page
    ...
}

The key code here is the two lines of code which mask the overflowed content due to the introduction of top and bottom padding.

doc.setFillColor(255, 255, 255);
doc.rect(0, 0, pdfWidth, topPadding, 'F'); // padding top
doc.rect(0, pageHeight - bottomPadding, pdfWidth, bottomPadding, 'F'); // padding bottom 

Example Calculation

Let’s break it down with numbers(the unit here can be mm, or others):

  • Assume the original webpage height is 180.
  • The page height is set to 100, with top and bottom margins of 10 each.
  • This means the actual content height(contentHeight) per page is 100 - 10 - 10 = 80.

First Page:

  • The position starts at 10.
  • The first 0-90 of the original webpage content is placed on the first page (since addImage() is called).
  • doc.rect() then masks the top 10 and bottom 10, leaving the 0-80 region of the original webpage visible.

Second Page:

  • The new position is (previous position - contentHeight) = (10 - 80) = -70.
  • This means the content from 70 to 170 of the original webpage is rendered on the second page.
  • Again, doc.rect() masks the 70-80(original webpage content) and 160-170 region, leaving 80-160 of the original content visible.

Since the padding creates a blank area at the start and end of each page, it ensures a clean and readable format without abrupt cuts at page breaks.

new.jpg

Hope this helps anyone facing the same issue! 

JSPDF MULTIPLE PAGE MARGIN BOTTOM MARGIN ADDIMAGE

  RELATED

  COMMENT

1
Ruben
Sep 5, 2026 at 12:02 pm
Устали от восторженных отзывов, которые заканчиваются ничем? Я предлагаю другой взгляд: делюсь только проверенными советами, рабочими схемами и эффективными алгоритмами для планирования маршрутов. Основа моего подхода заключается в следующем: путешествия — это не магия, а математика. Билеты, жильё, страховка и маршрут — всё это поддаётся разумному расчёту. Я помогу вам находить действительно стоящие варианты, чтобы отдых был ярким, а не разорительным. Здесь отсутствуют пустые обещания — только рабочий план, который сделает любую поездку проще и выгоднее. Туры https://t.me/tripkarta