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#
- Prerequisites
- Understanding the Truncation Problem
- Step-by-Step Solution
- Full Working Example
- Troubleshooting Common Issues
- Conclusion
- References
Prerequisites#
Before getting started, ensure you have the following:
- Basic knowledge of HTML, JavaScript, and ES6 (async/await).
jsPDFandhtml2canvaslibraries 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 html2canvasand import them.
- CDN: Add these scripts to your HTML:
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
scalefactor 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 usingyOffset / scale(sinceyOffsetis in scaled coordinates). - Chunk Height: For the last page, the chunk height may be smaller than
contentHeightto 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
scaledImgHeightandcontentHeight(useconsole.logto debug). IfscaledImgHeightis smaller than expected, thescalefactor may be incorrect. - Ensure
tempCanvas.heightis set tochunkHeight(not the fullcontentHeightfor the last page).
- Check
-
Blurry Images:
- Increase
html2canvas’sscaleoption (e.g.,scale: 3), but note this increases file size.
- Increase
-
Empty Pages:
- Verify
totalPagesis calculated correctly (e.g.,scaledImgHeight / contentHeightshould be > 1 for multi-page).
- Verify
-
CORS Errors:
- Add
useCORS: truetohtml2canvasoptions if capturing external images, and ensure the server allows CORS.
- Add
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.