I have even added some delay on mobile devices to give time to process, change the image format to reduce processing effort and resolution size, I have change the viewport and 10% of the times works and the rest 90% doesn't work, as you can see I have added console.logs to debug and usually the last log is right prior to do the canvas capture or while is doing the capture, here is my code, any suggestion?
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script>
document.getElementById('generate-pdf').addEventListener('click', function() {
const isMobile = screen.width < 1024; // Check if the device is mobile
if (isMobile) {
// Temporarily change the viewport for mobile devices to width=800
const originalViewportContent = document.querySelector('meta[name="viewport"]').getAttribute('content');
document.querySelector('meta[name="viewport"]').setAttribute('content', 'width=800');
console.log("Viewport temporarily set to width=800 for mobile.");
}
console.log("Starting PDF generation...");
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4'); // A4 size PDF
// Select the div content
const content = document.getElementById('pdf-content');
console.log("PDF content selected.");
// Get all elements you want to exclude (e.g., buttons with class 'exclude')
const elementsToExclude = content.querySelectorAll('.exclude');
console.log("Elements to exclude:", elementsToExclude);
// Temporarily hide the excluded elements
elementsToExclude.forEach(function(el) {
el.style.display = 'none';
});
console.log("Excluded elements hidden.");
// Get the company name from the page (you can change the selector to match the correct element)
const companyName = document.querySelector('.company-name').innerText.trim();
console.log("Company name found:", companyName);
// Default to 'Contract' if no company name is found
const fileName = companyName ? `${companyName} Contract.pdf` : 'Contract.pdf';
console.log("Generated filename:", fileName);
// Set lower scale for mobile devices to reduce file size
const scale = isMobile ? 1 : 2; // Lower scale on mobile to reduce file size
console.log("Using scale:", scale);
// If mobile, delay the html2canvas capture and image processing by 500ms
const delay = isMobile ? 500 : 0;
setTimeout(function() {
console.log("Starting html2canvas capture...");
// Start html2canvas capture (without options)
html2canvas(content).then(function(canvas) {
console.log("Canvas captured.");
// Determine the image format based on device type (PNG for desktop, JPEG for mobile)
const imgFormat = isMobile ? 'image/jpeg' : 'image/png';
console.log("Using image format:", imgFormat);
// Convert the canvas to a base64 image (JPEG for mobile, PNG for desktop)
setTimeout(function() {
const imgData = canvas.toDataURL(imgFormat); // Switch format based on device type
console.log("Image data created.");
// Get the dimensions of the canvas
const imgWidth = canvas.width;
const imgHeight = canvas.height;
console.log("Canvas dimensions:", imgWidth, imgHeight);
// A4 page dimensions in points
const pageWidth = 595.28;
const pageHeight = 841.89;
// Calculate scaling for A4 page
const scaleX = pageWidth / imgWidth;
const scaleY = pageHeight / imgHeight;
const finalScale = Math.min(scaleX, scaleY);
console.log("Calculated scaling:", finalScale);
// Center the image on the PDF
const xOffset = (pageWidth - imgWidth * finalScale) / 2;
const yOffset = (pageHeight - imgHeight * finalScale) / 2;
console.log("Image positioning: xOffset:", xOffset, "yOffset:", yOffset);
// Delay before adding the image to the PDF (500ms for mobile only)
setTimeout(function() {
doc.addImage(imgData, imgFormat.toUpperCase(), xOffset, yOffset, imgWidth * finalScale, imgHeight * finalScale, undefined, 'FAST');
console.log("Image added to PDF.");
// Delay before saving the PDF (500ms for mobile only)
setTimeout(function() {
// Save the generated PDF with the dynamically created filename
doc.save(fileName);
console.log("PDF saved:", fileName);
// Restore the visibility of the excluded elements
elementsToExclude.forEach(function(el) {
el.style.display = ''; // Restore original display
});
console.log("Excluded elements restored.");
// Ensure the viewport is restored after all steps are complete
if (isMobile) {
const viewportMeta = document.querySelector('meta[name="viewport"]');
if (viewportMeta) {
viewportMeta.setAttribute('content', originalViewportContent);
console.log("Viewport restored to original settings.");
} else {
console.error("Error: Viewport meta tag not found.");
}
}
}, 500); // Delay before saving PDF (500ms for mobile)
}, 500); // Delay before adding image to PDF (500ms)
}, 500); // Delay before converting canvas to image data (500ms)
}).catch(function(error) {
console.error('Error during capture: ', error);
// Restore the visibility of the excluded elements in case of error
elementsToExclude.forEach(function(el) {
el.style.display = ''; // Restore original display
});
// Restore the original viewport settings
if (isMobile) {
const viewportMeta = document.querySelector('meta[name="viewport"]');
if (viewportMeta) {
viewportMeta.setAttribute('content', originalViewportContent);
console.log("Viewport restored to original settings.");
} else {
console.error("Error: Viewport meta tag not found.");
}
}
});
}, delay); // Only apply the delay for mobile
});
</script>
Finally made it work with different tips that other people provided in different posts, for example change all the images on the website from lazy to eager while doing the capture, also change the viewport widht and height, also adding some delay in the whole process to give time to do it correctly, and this is how the final code look like.
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script>
document.getElementById('generate-pdf').addEventListener('click', function() {
const isMobile = screen.width < 1024; // Check if the device is mobile
let lazyImagesChanged = false; // Flag to track if lazy loading was changed
// Only modify the viewport and images if it's a mobile device
if (isMobile) {
const viewportMeta = document.querySelector('meta[name="viewport"]');
if (!viewportMeta) {
console.error("Viewport meta tag not found.");
return;
}
const originalViewportContent = viewportMeta.getAttribute('content'); // Store the original viewport content
viewportMeta.setAttribute('content', 'width=800'); // Temporarily change the viewport for mobile
console.log("Viewport temporarily set to width=800 for mobile.");
// Change lazy-loaded images to eager load before starting the capture
const lazyImages = document.querySelectorAll('img[loading="lazy"]');
if (lazyImages.length > 0) {
lazyImages.forEach(img => {
img.setAttribute('loading', 'eager');
});
console.log("Lazy-loaded images set to eager load for capture.");
lazyImagesChanged = true; // Mark that images were changed
}
// Start the PDF generation process after changing the viewport
generatePDF(originalViewportContent, viewportMeta, isMobile, lazyImagesChanged); // Pass the flag to the function
} else {
// For desktop devices, directly start the process without modifying the viewport
console.log("Desktop device detected, starting PDF generation...");
generatePDF(null, null, isMobile, false); // No need to change or pass the viewport meta for desktop
}
});
// Function to generate the PDF
function generatePDF(originalViewportContent, viewportMeta, isMobile, lazyImagesChanged) {
console.log("Starting PDF generation...");
const { jsPDF } = window.jspdf;
const doc = new jsPDF('p', 'pt', 'a4'); // A4 size PDF
// Select the div content
const content = document.getElementById('pdf-content');
console.log("PDF content selected.");
// Get all elements you want to exclude (e.g., buttons with class 'exclude')
const elementsToExclude = content.querySelectorAll('.exclude');
console.log("Elements to exclude:", elementsToExclude);
// Temporarily hide the excluded elements
elementsToExclude.forEach(function(el) {
el.style.display = 'none';
});
console.log("Excluded elements hidden.");
// Get the company name from the page (you can change the selector to match the correct element)
const companyName = document.querySelector('.company-name').innerText.trim();
console.log("Company name found:", companyName);
// Default to 'Contract' if no company name is found
const fileName = companyName ? `${companyName} Contract.pdf` : 'Contract.pdf';
console.log("Generated filename:", fileName);
// Set lower scale for mobile devices to reduce file size
const scale = isMobile ? 1 : 2; // Lower scale on mobile to reduce file size
console.log("Using scale:", scale);
// If mobile, delay the html2canvas capture and image processing by 500ms
const delay = isMobile ? 500 : 0;
setTimeout(function() {
console.log("Starting html2canvas capture...");
// Start html2canvas capture (without options)
html2canvas(content).then(function(canvas) {
console.log("Canvas captured.");
// Determine the image format based on device type (PNG for desktop, JPEG for mobile)
const imgFormat = isMobile ? 'image/jpeg' : 'image/png';
console.log("Using image format:", imgFormat);
// Convert the canvas to a base64 image (JPEG for mobile, PNG for desktop)
setTimeout(function() {
const imgData = canvas.toDataURL(imgFormat); // Switch format based on device type
console.log("Image data created.");
// Get the dimensions of the canvas
const imgWidth = canvas.width;
const imgHeight = canvas.height;
console.log("Canvas dimensions:", imgWidth, imgHeight);
// A4 page dimensions in points
const pageWidth = 595.28;
const pageHeight = 841.89;
// Calculate scaling for A4 page
const scaleX = pageWidth / imgWidth;
const scaleY = pageHeight / imgHeight;
const finalScale = Math.min(scaleX, scaleY);
console.log("Calculated scaling:", finalScale);
// Center the image on the PDF
const xOffset = (pageWidth - imgWidth * finalScale) / 2;
const yOffset = (pageHeight - imgHeight * finalScale) / 2;
console.log("Image positioning: xOffset:", xOffset, "yOffset:", yOffset);
// Delay before adding the image to the PDF (500ms for mobile only)
setTimeout(function() {
doc.addImage(imgData, imgFormat.toUpperCase(), xOffset, yOffset, imgWidth * finalScale, imgHeight * finalScale, undefined, 'FAST');
console.log("Image added to PDF.");
// Delay before saving the PDF (500ms for mobile only)
setTimeout(function() {
// Save the generated PDF with the dynamically created filename
doc.save(fileName);
console.log("PDF saved:", fileName);
// Restore the visibility of the excluded elements
elementsToExclude.forEach(function(el) {
el.style.display = ''; // Restore original display
});
console.log("Excluded elements restored.");
// Ensure the viewport is restored after all steps are complete
if (originalViewportContent && viewportMeta) {
viewportMeta.setAttribute('content', originalViewportContent);
console.log("Viewport restored to original settings.");
}
// Restore the images back to lazy loading only if they were changed at the beginning
if (lazyImagesChanged) {
const lazyImages = document.querySelectorAll('img[loading="eager"]');
lazyImages.forEach(img => {
img.setAttribute('loading', 'lazy');
});
console.log("Images restored to lazy loading.");
}
// Reload the page after restoring the viewport and images, but only if it's mobile
if (isMobile) {
setTimeout(function() {
window.location.reload(); // Reload the page after 500ms delay
console.log("Page reloaded.");
}, 500); // Delay to ensure everything is restored
}
}, 500); // Delay before saving PDF (500ms for mobile)
}, 500); // Delay before adding image to PDF (500ms)
}, 500); // Delay before converting canvas to image data (500ms)
}).catch(function(error) {
console.error('Error during capture: ', error);
// Restore the visibility of the excluded elements in case of error
elementsToExclude.forEach(function(el) {
el.style.display = ''; // Restore original display
});
// Restore the original viewport settings
if (originalViewportContent && viewportMeta) {
viewportMeta.setAttribute('content', originalViewportContent);
console.log("Viewport restored to original settings.");
}
// Restore the images back to lazy loading only if they were changed at the beginning
if (lazyImagesChanged) {
const lazyImages = document.querySelectorAll('img[loading="eager"]');
lazyImages.forEach(img => {
img.setAttribute('loading', 'lazy');
});
console.log("Images restored to lazy loading.");
}
// Reload the page after restoring the viewport and images, but only if it's mobile
if (isMobile) {
setTimeout(function() {
window.location.reload(); // Reload the page after 500ms delay
console.log("Page reloaded.");
}, 500); // Delay to ensure everything is restored
}
});
}, delay); // Only apply the delay for mobile
}
</script>