mobilehtml2canvas

Html2Canvas not working on mobile devices


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>

Solution

  • 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>