How to Save a Large Image in Multiple PDF Pages Using jsPDF: Fix for html2canvas-Generated Images Truncation

Converting HTML content to PDF is a common requirement in web development, often achieved using libraries like html2canvas (to capture HTML as an image) and jsPDF (to generate PDFs). However, a frequent frustration arises when dealing with large images (e.g., long scrollable content): the generated PDF truncates the image, cutting off the bottom portion. This happens because jsPDF defaults to a single page, and if the image exceeds the page height, it gets cropped.

In this blog, we’ll dive into why this truncation occurs and provide a step-by-step solution to split large images into multiple PDF pages. By the end, you’ll be able to generate PDFs with tall images that span multiple pages seamlessly.

Table of Contents#

  1. Prerequisites
  2. Understanding the Truncation Problem
  3. Step-by-Step Solution
  4. Full Working Example
  5. Troubleshooting Common Issues
  6. Conclusion
  7. References

Prerequisites#

Before getting started, ensure you have the following:

  • Basic knowledge of HTML, JavaScript, and ES6 (async/await).
  • jsPDF and html2canvas libraries installed. Use either:
    • CDN: Add these scripts to your HTML:
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>  
      <script src="https://html2canvas.hertzen.com/dist/html2canvas.min.js"></script>  
    • npm: Install via npm install jspdf html2canvas and import them.

Understanding the Truncation Problem#

When you use html2canvas to capture a large HTML element (e.g., a long report or scrollable div), it generates a single canvas/image. If this image’s height exceeds the PDF page height (e.g., A4 portrait is 297mm tall), jsPDF’s addImage() method will only render the portion that fits on one page, truncating the rest.

Example of the Truncation Issue#

Here’s a basic code snippet that captures an HTML element and saves it as a PDF—but truncates tall content:

import { jsPDF } from 'jspdf';  
import html2canvas from 'html2canvas';  
 
async function generatePDF() {  
  const { jsPDF } = window.jspdf;  
  const pdf = new jsPDF('p', 'mm', 'a4'); // Portrait, mm units, A4 size  
 
  const element = document.getElementById('large-content');  
  const canvas = await html2canvas(element);  
  const imgData = canvas.toDataURL('image/png');  
 
  // Add image to PDF (truncates if too tall!)  
  pdf.addImage(imgData, 'PNG', 10, 10, 190, canvas.height * (190 / canvas.width));  
  pdf.save('truncated-document.pdf');  
}  

Why it fails: The addImage() method renders the entire image on one page. If canvas.height * scale exceeds the A4 page height (297mm), the bottom is cut off.

Step-by-Step Solution#

To fix truncation, we’ll split the large image into smaller, page-sized vertical chunks and render each chunk on a separate PDF page. Here’s how:

Step 1: Capture HTML Content with html2canvas#

First, use html2canvas to capture the target HTML element as a canvas. This gives us the full image data, including the “tall” content.

const element = document.getElementById('large-content');  
const canvas = await html2canvas(element, {  
  scale: 2, // Higher scale = better resolution (adjust as needed)  
  useCORS: true // If capturing external images  
});  
const imgWidth = canvas.width;  
const imgHeight = canvas.height;  

Step 2: Configure jsPDF and Page Settings#

Initialize jsPDF with your desired page format (e.g., A4), and define page dimensions, margins, and available content area (page size minus margins).

const pdf = new jsPDF('p', 'mm', 'a4'); // Portrait (p), mm units, A4  
const pageWidth = pdf.internal.pageSize.getWidth(); // A4 width: ~210mm  
const pageHeight = pdf.internal.pageSize.getHeight(); // A4 height: ~297mm  
 
const margin = 10; // 10mm margin on all sides  
const contentWidth = pageWidth - 2 * margin; // Available width for content  
const contentHeight = pageHeight - 2 * margin; // Available height for content  

Step 3: Calculate Image Scaling#

To avoid stretching or overflow, scale the image proportionally to fit the available content width. This ensures the image isn’t wider than the page.

const scale = contentWidth / imgWidth; // Scale factor to fit width  
const scaledImgWidth = imgWidth * scale; // Scaled image width  
const scaledImgHeight = imgHeight * scale; // Scaled image height (may exceed page height)  

Step 4: Determine Number of Pages#

Calculate how many PDF pages are needed by dividing the scaled image height by the available content height.

const totalPages = Math.ceil(scaledImgHeight / contentHeight);  
console.log(`Total pages needed: ${totalPages}`);  

Step 5: Split the Image into Page-Sized Chunks#

Use a temporary canvas to extract vertical “slices” (chunks) of the large image. Each chunk will fit within the available content height.

How It Works:#

  • For each page, calculate the vertical offset of the chunk (e.g., page 0: 0–contentHeight, page 1: contentHeight–2*contentHeight, etc.).
  • Draw this chunk onto a temporary canvas using drawImage(), which lets you crop a portion of the original image.
  • Convert the temporary canvas to an image URL and add it to the PDF.

Step 6: Render Chunks to PDF Pages#

Loop through each page, extract the chunk, and add it to the PDF. Add new pages as needed.

for (let page = 0; page < totalPages; page++) {  
  // Calculate vertical offset for the current chunk  
  const yOffset = page * contentHeight;  
 
  // Create a temporary canvas for the chunk  
  const tempCanvas = document.createElement('canvas');  
  const tempCtx = tempCanvas.getContext('2d');  
 
  // Set temp canvas size to match the scaled chunk dimensions  
  tempCanvas.width = scaledImgWidth;  
  tempCanvas.height = Math.min(contentHeight, scaledImgHeight - yOffset); // Last chunk may be smaller  
 
  // Draw the chunk from the original canvas onto the temp canvas  
  tempCtx.drawImage(  
    canvas,  
    0, // Source x (left of original image)  
    yOffset / scale, // Source y (top of chunk in original image, adjusted for scale)  
    imgWidth, // Source width (full width of original image)  
    tempCanvas.height / scale, // Source height (chunk height in original image, adjusted for scale)  
    0, // Destination x (left of temp canvas)  
    0, // Destination y (top of temp canvas)  
    scaledImgWidth, // Destination width (scaled to fit page)  
    tempCanvas.height // Destination height (chunk height)  
  );  
 
  // Convert temp canvas to image data  
  const chunkImgData = tempCanvas.toDataURL('image/png');  
 
  // Add chunk to PDF (position: margin, margin)  
  pdf.addImage(  
    chunkImgData,  
    'PNG',  
    margin, // x position (left margin)  
    margin, // y position (top margin)  
    scaledImgWidth, // width (fits content area)  
    tempCanvas.height // height (chunk height)  
  );  
 
  // Add new page if not the last page  
  if (page < totalPages - 1) {  
    pdf.addPage();  
  }  
}  
 
// Save the final PDF  
pdf.save('multi-page-document.pdf');  

Key Explanations:#

  • Scaling: The scale factor ensures the image fits the PDF page width without overflow.
  • Temporary Canvas: Used to crop the original image into page-sized chunks. The drawImage() method crops the source image using yOffset / scale (since yOffset is in scaled coordinates).
  • Chunk Height: For the last page, the chunk height may be smaller than contentHeight to avoid empty space.

Full Working Example#

Here’s the complete code combining all steps:

import { jsPDF } from 'jspdf';  
import html2canvas from 'html2canvas';  
 
async function generateMultiPagePDF() {  
  try {  
    // Step 1: Capture HTML element  
    const element = document.getElementById('large-content');  
    const canvas = await html2canvas(element, { scale: 2, useCORS: true });  
    const imgWidth = canvas.width;  
    const imgHeight = canvas.height;  
 
    // Step 2: Configure PDF settings  
    const pdf = new jsPDF('p', 'mm', 'a4');  
    const pageWidth = pdf.internal.pageSize.getWidth();  
    const pageHeight = pdf.internal.pageSize.getHeight();  
    const margin = 10;  
    const contentWidth = pageWidth - 2 * margin;  
    const contentHeight = pageHeight - 2 * margin;  
 
    // Step 3: Calculate scaling  
    const scale = contentWidth / imgWidth;  
    const scaledImgHeight = imgHeight * scale;  
    const totalPages = Math.ceil(scaledImgHeight / contentHeight);  
 
    // Step 4 & 5: Split image into chunks and add to PDF  
    for (let page = 0; page < totalPages; page++) {  
      const yOffset = page * contentHeight;  
      const chunkHeight = Math.min(contentHeight, scaledImgHeight - yOffset);  
 
      // Create temp canvas for chunk  
      const tempCanvas = document.createElement('canvas');  
      tempCanvas.width = scaledImgWidth;  
      tempCanvas.height = chunkHeight;  
      const tempCtx = tempCanvas.getContext('2d');  
 
      // Draw chunk from original canvas  
      tempCtx.drawImage(  
        canvas,  
        0,  
        yOffset / scale,  
        imgWidth,  
        chunkHeight / scale,  
        0,  
        0,  
        scaledImgWidth,  
        chunkHeight  
      );  
 
      // Add chunk to PDF  
      const chunkImgData = tempCanvas.toDataURL('image/png');  
      pdf.addImage(chunkImgData, 'PNG', margin, margin, scaledImgWidth, chunkHeight);  
 
      // Add new page if needed  
      if (page < totalPages - 1) pdf.addPage();  
    }  
 
    pdf.save('multi-page-document.pdf');  
  } catch (error) {  
    console.error('PDF generation failed:', error);  
  }  
}  
 
// Trigger PDF generation (e.g., on button click)  
document.getElementById('generate-pdf-btn').addEventListener('click', generateMultiPagePDF);  

Troubleshooting Common Issues#

  • Truncation Still Occurs:

    • Check scaledImgHeight and contentHeight (use console.log to debug). If scaledImgHeight is smaller than expected, the scale factor may be incorrect.
    • Ensure tempCanvas.height is set to chunkHeight (not the full contentHeight for the last page).
  • Blurry Images:

    • Increase html2canvas’s scale option (e.g., scale: 3), but note this increases file size.
  • Empty Pages:

    • Verify totalPages is calculated correctly (e.g., scaledImgHeight / contentHeight should be > 1 for multi-page).
  • CORS Errors:

    • Add useCORS: true to html2canvas options if capturing external images, and ensure the server allows CORS.

Conclusion#

By splitting large images into page-sized chunks, we solve the truncation issue in jsPDF when using html2canvas. This method ensures all content is rendered across multiple PDF pages, with proper scaling and margins. Adjust scale, margin, and contentHeight to fine-tune resolution and layout.

References#