HTML zu PDF in React konvertieren mit jsPDF und html2canvas: Schritt-für-Schritt-Anleitung

Melden

In vielen Webanwendungen – sei es für Rechnungen, Berichte, Tickets oder Zertifikate – ist der Export von Webinhalten als PDF eine essenzielle Funktion.

Der beliebteste und flexibelste Ansatz in React ist die Kombination von html2canvas und jsPDF:

  1. html2canvas nimmt den ausgewählten HTML-Bereich und wandelt ihn in ein hochauflösendes Bild (Canvas) um.
  2. jsPDF nimmt dieses Bild und bettet es in ein neu erstelltes PDF-Dokument ein, das anschließend heruntergeladen werden kann.

In diesem Tutorial lernst du, wie du diese Funktionalität Schritt für Schritt in deine React-Anwendung integrierst.


1. Installation der Abhängigkeiten

Öffne dein Terminal im Root-Verzeichnis deines React-Projekts und installiere die beiden benötigten Pakete:

npm install jspdf html2canvas

Alternativ mit Yarn:

yarn add jspdf html2canvas

2. Der React-Code

Erstelle eine neue Komponente, z. B. PdfExportComponent.jsx.

Wir nutzen useRef, um das DOM-Element zu markieren, welches in das PDF konvertiert werden soll.

import React, { useRef } from 'react';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';

const PdfExportComponent = () => {
  // Referenz auf das HTML-Element, das exportiert werden soll
  const pdfRef = useRef(null);

  const handleDownloadPdf = async () => {
    const element = pdfRef.current;

    if (!element) {
      return;
    }

    try {
      // 1. HTML in ein Canvas (Bild) umwandeln
      // 'scale: 2' sorgt für eine schärfere Textdarstellung im PDF
      const canvas = await html2canvas(element, {
        scale: 2,
        useCORS: true // Wichtig, falls externe Bilder geladen werden
      });

      // 2. Bilddaten im PNG-Format extrahieren
      const imgData = canvas.toDataURL('image/png');

      // 3. Neues jsPDF-Dokument instanziieren (Format: DIN A4, Einheit: mm)
      const pdf = new jsPDF('portrait', 'mm', 'a4');

      // Dimensionen berechnen, um das Bild proportional anzupassen
      const pdfWidth = pdf.internal.pageSize.getWidth();
      const pdfHeight = (canvas.height * pdfWidth) / canvas.width;

      // 4. Bild zum PDF hinzufügen (x=0, y=0)
      pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);

      // 5. PDF herunterladen
      pdf.save('mein-dokument.pdf');
    } catch (error) {
      console.error('Fehler beim Erstellen der PDF:', error);
    }
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
      {/* Dieser Bereich wird exportiert */}
      <div 
        ref={pdfRef} 
        style={{ 
          padding: '24px', 
          backgroundColor: '#ffffff', 
          border: '1px solid #e0e0e0',
          borderRadius: '8px',
          maxWidth: '600px',
          margin: '0 auto'
        }}
      >
        <h1 style={{ color: '#333' }}>Rechnung / Bericht</h1>
        <p>Dies ist ein Beispielinhalt, der dynamisch in React gerendert wird.</p>

        <table style={{ width: '100%', marginTop: '20px', borderCollapse: 'collapse' }}>
          <thead>
            <tr style={{ borderBottom: '2px solid #ccc', textAlign: 'left' }}>
              <th>Position</th>
              <th>Menge</th>
              <th>Preis</th>
            </tr>
          </thead>
          <tbody>
            <tr style={{ borderBottom: '1px solid #eee' }}>
              <td>Webentwicklung Service</td>
              <td>1</td>
              <td>500,00 €</td>
            </tr>
            <tr style={{ borderBottom: '1px solid #eee' }}>
              <td>PDF-Export Feature</td>
              <td>1</td>
              <td>150,00 €</td>
            </tr>
          </tbody>
        </table>

        <h3 style={{ marginTop: '20px', textAlign: 'right' }}>Gesamt: 650,00 €</h3>
      </div>

      {/* Button zum Auslösen des Downloads */}
      <div style={{ textAlign: 'center', marginTop: '20px' }}>
        <button 
          onClick={handleDownloadPdf}
          style={{
            padding: '10px 20px',
            fontSize: '16px',
            cursor: 'pointer',
            backgroundColor: '#007bff',
            color: '#fff',
            border: 'none',
            borderRadius: '4px'
          }}
        >
          Als PDF herunterladen
        </button>
      </div>
    </div>
  );
};

export default PdfExportComponent;

3. Funktionsweise im Detail

  • useRef(null): Wir hängen die Referenz an das <div>, das gedruckt werden soll. Dadurch weiß html2canvas exakt, welcher Bereich gerendert werden muss, ohne den Rest der Seite (wie den Download-Button) mitzunehmen.
  • scale: 2 (in html2canvas): Browser rendern standardmäßig mit 96 DPI, was im PDF schnell pixelig wirkt. Durch die Skalierung verdoppeln wir die Auflösung für ein scharfes Druckergebnis.
  • Proportionale Skalierung:
    const pdfHeight = (canvas.height * pdfWidth) / canvas.width;

    Diese Formel stellt sicher, dass das Seitenverhältnis des HTML-Inhalts auf die Seitenbreite des A4-Formats skaliert wird, ohne dass Inhalte verzerrt werden.

  • pdf.save('name.pdf'): Löst direkt den Browser-Download mit dem angegebenen Dateinamen aus.

4. Wichtige Tipps für die Praxis

  1. Bilder und CORS: Wenn in deinem zu druckenden HTML externe Bilder liegen, aktiviere immer useCORS: true in den html2canvas-Optionen. Andernfalls verweigert der Browser das Auslesen des Canvas aus Sicherheitsgründen.

  2. Mehrseitige Dokumente (Pagination): Wenn dein Inhalt länger als eine DIN-A4-Seite ist, wird er mit dem obigen Code unten abgeschnitten. Für mehrseitige Dokumente muss die Höhe in einer Schleife geprüft und bei Bedarf pdf.addPage() aufgerufen werden:

    let heightLeft = pdfHeight;
    let position = 0;
    const pageHeight = pdf.internal.pageSize.getHeight();
    
    pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
    heightLeft -= pageHeight;
    
    while (heightLeft > 0) {
      position = heightLeft - pdfHeight;
      pdf.addPage();
      pdf.addImage(imgData, 'PNG', 0, position, pdfWidth, pdfHeight);
      heightLeft -= pageHeight;
    }
  3. CSS-Einschränkungen: html2canvas unterstützt die meisten CSS-Eigenschaften (Flexbox, Grid, Border-Radius), interpretiert manche modernen Effekte wie komplexe box-shadow oder neuartige CSS-Filter jedoch manchmal ungenau. Halte das Druck-Layout daher möglichst sauber.

0