0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】html2canvas と jsPDF を使ってWebページのデザインを複数ページのPDFファイルにする

Posted at

はじめに

この記事では、html2canvas と jsPDF を使ってWebページのデザインを複数ページのPDFファイルにする方法を記載します。

以下の記事では、html2canvas と jsPDF を使ってWebページのデザインを1ページのPDFファイルにする方法を記載しました。

ただ、PDF化する対象のWebページのサイズによってはA4サイズに収まらないことがあります。特に、長いリストや表を含む場合には、1ページに収まらず、想定しない見た目になる可能性があります。

この問題を解決するために、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

実装方針

PDF1ページ分の高さごとにWebページ画像を描画します。

  • pdfHeigh: PDF1ページ分の高さ
  • position: 描画開始位置
ページ1
長いコンテンツの一部
(position = 0)

+---------------------------+

ページ2
続きのコンテンツ
(position = pdfHeight)

+---------------------------+

ページx
続きのコンテンツ
(position = (x-1) * pdfHeight)

PDFのページ追加は jsPDF の addPage() を利用します。

これを描画開始位置がWebページ画像の高さになるまで繰り返します。

コード

コードにすると以下のようになります。

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

export function PdfExportWithPageSplitting() {
  /** 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 = pdf.internal.pageSize.getHeight();

      // 5️. 画像の幅と高さを取得
      const imageWidth = canvas.width;
      const imageHeight = canvas.height;

      // 6️. 画像をPDFにフィットさせるための比率を計算
      const ratio = pdfWidth / imageWidth;
      // 7. 画像全体をPDFに合わせた場合の高さを計算
      const scaledHeight = imageHeight * ratio;
      /** 現在の描画開始位置(スケール後の画像上でのY座標) */
      let position = 0;

      // 8. 画像全体が描画されるまでループ
      // 条件:position(描画開始位置)がscaledHeight(スケール後の画像全体の高さ)より小さい間
      while (position < scaledHeight) {
        // 9. 画像を追加
        pdf.addImage({
          imageData: imageData,
          format: "PNG",
          x: 0,
          // 前ページで描画した分を上方向へずらし、新しいページに適切な部分が表示されるように調整
          y: -position,
          width: pdfWidth,
          height: scaledHeight,
        });
        // 10. 1ページ分の高さを加算(ページごとに pdfHeight 分だけ増やす)
        position += pdfHeight;
        // 11. まだ描画していない画像部分が残っている場合は新しいページを追加
        if (position < scaledHeight) {
          pdf.addPage();
        }
      }
      // 12. PDFを保存
      pdf.save("document.pdf");
    } catch (error) {
      console.error("PDF生成中にエラーが発生しました:", error);
    }
  };

  return (
    <div>
      {/* PDFに変換する対象のエリア */}
      <div
        ref={contentRef}
        style={{ padding: 24, backgroundColor: "blue", width: 500 }}
      >
        <h2>PDF化するコンテンツ</h2>
        <p>この部分がPDFとして出力されます。</p>
        {[...Array(100)].map((_, i) => (
          <p key={i}>
            さらに長いコンテンツを追加して、ページ分割が発生することを確認します。
          </p>
        ))}
      </div>

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

動作確認

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

Screen-Recording-2025-02-14-083631.gif

PDF化対象のWebページが増えるほど、PDFのダウンロードには時間がかかります。また、PDFダウンロー自体ができないこともあります。
ちなみに今回確認した環境(Windows11、Core i7、16GB)では、PDF50ページ分まではダウンロードできました。

Screen-Recording-2025-02-14-084553.gif

ただ、それ以上のサイズだとエラーになり、PDFダウンロードができませんでした。

image.png

参考

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?