【React】html2canvas と jsPDF を使ってWebページのデザインをそのままPDF化

# はじめに
この記事では、html2canvas と jsPDF を使ってWebページのデザインをそのままPDF化する方法を記載します。

https://html2canvas.hertzen.com/

https://artskydj.github.io/jsPDF/docs/jsPDF.html

以下の記事では、React-pdf を使ったPDF作成方法を記載していますが、React-pdf では独自のコンポーネントを利用して、PDFのUIを実装する必要があります。

https://qiita.com/Yasushi-Mo/items/93aecc0a204eb683d6bc

一方、今回紹介する html2canvas と jsPDF を使えば、WebページのデザインをそのままPDF化できます。
各ライブラリの役割は以下の通りです。

- `html2canvas`: ブラウザ上のHTML要素を画像としてキャプチャできるライブラリ
- `jsPDF`: 画像をPDFとして保存できるJavaScriptライブラリ

この2つを組み合わせることで、現在のWebページをそのままPDF化し、ユーザーにダウンロードさせることが簡単にできます。

:::note
各ライブラリの直近1年間のダウンロード数は以下の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/8ec4b69f-7baf-536f-0667-86fa5f82a4f2.png)

:::

https://npmtrends.com/html2canvas-vs-jspdf-vs-react-pdf

# 開発環境
開発環境は以下の通りです。

- html2canvas 1.4.1
- jspdf 2.5.2
- Windows11
- React 18.3.1
- TypeScript 5.7.3
- Node.js 22.13.1
- npm 11.0.0

# インストール
まずは html2canvas と jsPDF をインストールします。

```powershell
npm install html2canvas jspdf
```

# PDF化対象のWebページのデザイン実装
次にPDF化対象のWebページのデザインを実装します。
今回は useRef を渡した範囲のエリアをPDF化します。

```PdfExportSample.tsx
import { useRef } from "react";

export function PdfExportSample() {
  /** PDF化する対象の要素を参照するための useRef */
  const contentRef = useRef<HTMLDivElement>(null);

  return (
    <div>
      {/* PDFに変換する対象のエリア */}
      <div ref={contentRef} style={{ padding: 24, backgroundColor: "blue" }}>
        <h2>PDF化するコンテンツ</h2>
        <p>この部分がPDFとして出力されます。</p>
      </div>

      {/* PDFダウンロードボタン */}
      <button style={{ backgroundColor: "blue" }}>PDFをダウンロード</button>
    </div>
  );
}

```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/18004a4e-e961-2269-8257-297cb7838221.png)

# PDF化処理の実装
最後に先ほど実装した箇所をPDF化する処理を実装します。
完成形は以下の通りです。処理順に解説していきます。

```PdfExportSample.tsx
import html2canvas from "html2canvas";
import jsPDF from "jspdf";
import { useRef } from "react";

export function PdfExportSample() {
  /** PDF化する対象の要素を参照するための useRef */
  const contentRef = useRef<HTMLDivElement>(null);

  /** PDFダウンロード処理 */
  const handleDownloadPdf = async () => {
    if (!contentRef.current) return;

    try {
      // 1️⃣ 指定した要素をキャプチャしてCanvasに変換
      const canvas = await html2canvas(contentRef.current);

      // 2️⃣ Canvasを画像として取得（Base64のPNGデータ）
      const imageData = canvas.toDataURL("image/png");

      // 3️⃣ jsPDF インスタンスを作成（A4縦向き）
      const pdf = new jsPDF({ orientation: "p", unit: "mm", format: "a4"});

      // 4️⃣ PDFの幅を取得し、アスペクト比を維持した高さを計算
      const pdfWidth = pdf.internal.pageSize.getWidth();
      const pdfHeight = (canvas.height * pdfWidth) / canvas.width;

      // 5️⃣ 画像をPDFに追加（左上から配置）
      pdf.addImage({
        imageData: imageData,
        format: "PNG",
        x: 0,
        y: 0,
        width: pdfWidth,
        height: pdfHeight,
      });

      // 6️⃣ PDFをダウンロード
      pdf.save("document.pdf");
    } catch (error) {
      console.error("PDF生成中にエラーが発生しました:", error);
    }
  };

  return (
    <div>
      {/* PDFに変換する対象のエリア */}
      <div ref={contentRef} style={{ padding: 24, backgroundColor: "blue" }}>
        <h2>PDF化するコンテンツ</h2>
        <p>この部分がPDFとして出力されます。</p>
      </div>

      {/* PDFダウンロードボタン */}
      <button onClick={handleDownloadPdf} style={{ backgroundColor: "blue" }}>
        PDFをダウンロード
      </button>
    </div>
  );
}

```

## 1️⃣ 指定した要素をキャプチャしてCanvasに変換

```PdfExportSample.tsx
const canvas = await html2canvas(contentRef.current);
```

`html2canvas(contentRef.current)` を呼び出すことで、指定した要素（`contentRef`）を Canvas（画像データ）に変換します。言い換えると、`HTMLDivElement` を `HTMLCanvasElement` に変換します。
この処理により、Webページのデザインをそのまま画像として取得 できます。


## 2️⃣ Canvasを画像として取得（Base64のPNGデータ）

```PdfExportSample.tsx
const imgData = canvas.toDataURL("image/png");
```
`canvas.toDataURL("image/png")` を使うことで、CanvasをPNG画像としてエンコード（Base64形式） できます。

https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL

`imgData` には `"data:image/png;base64,...."` のような文字列が入ります。

## 3️⃣ jsPDF インスタンスを作成（A4縦向き）

```PdfExportSample.tsx
const pdf = new jsPDF({ orientation: "p", unit: "mm", format: "a4"});
```
`new jsPDF({ orientation: "p", unit: "mm", format: "a4"})` で A4サイズのPDF（縦向き） を作成します。

- `"p"` : 縦向き（`"portrait"` のショートカット）
    - 横向きは `"l"` or `"landscape"`
- `"mm"` : 単位をミリメートル に指定
- `"a4"` : A4サイズ（210mm × 297mm）

https://artskydj.github.io/jsPDF/docs/jsPDF.html

## 4️⃣ PDFの幅を取得し、アスペクト比を維持した高さを計算

```PdfExportSample.tsx
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
```

画像をPDFの幅いっぱいに拡大し、アスペクト比を保持するために高さを計算します。

## 5️⃣ 画像をPDFに追加（左上から配置）

```PdfExportSample.tsx
pdf.addImage({
  imageData: imgData,
  format: "PNG",
  x: 0,
  y: 0,
  width: pdfWidth,
  height: pdfHeight,
});
```

`pdf.addImage({ imageData: imgData, format: "PNG", x: 0, y: 0, width: pdfWidth, height: pdfHeight})` で、左上 (0,0) に画像を追加します。

https://artskydj.github.io/jsPDF/docs/module-addImage.html

## 6️⃣ PDFをダウンロード

```PdfExportSample.tsx
pdf.save("document.pdf");
```

`pdf.save("document.pdf")` を呼び出すことで、PDFをダウンロード できます。`"document.pdf"` はPDFファイル名になります。

# 動作確認
動作確認をします。「PDFをダウンロード」ボタンをクリックすると、実装時に指定した範囲のWebページがPDFファイルとして出力されます。

![Screen-Recording-2025-02-12-095920.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/f75bbcac-db29-56dc-e62e-43d581d925f4.gif)

:::note warn
Webページを一度画像に変換してからPDF化しているため、PDFファイル内の文字検索等はできません。
:::

# まとめ
この記事では、html2canvas と jsPDF を使ってWebページのデザインをそのままPDF化する方法を記載しました。この方法を使えば、どんなWebページでも手軽にPDF化できます。
今後はページ分割方法やPDFファイルの容量が増えた時の対応法なども試していきたいです。

# 参考
- [html2canvas](https://html2canvas.hertzen.com/)
- [html2canvas vs jspdf vs react-pdf](https://npmtrends.com/html2canvas-vs-jspdf-vs-react-pdf)
- [HTMLCanvasElement: toDataURL() method](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL)
- [jsPDF](https://artskydj.github.io/jsPDF/docs/jsPDF.html)