メインコンテンツまでスキップ

バックエンド不要!html2canvasとjsPDFで実装するReactのPDF生成

· 約8分
山本

こんにちは。サービス開発部プロダクトエンジニアの山本です。

今回は16タイプマッピングという機能開発において、バックエンドを介さずフロントエンドのみでPDF生成を実装した経験についてお話しします。

PDFを生成する場合、一般的にバックエンドを介して処理を行いますが、今回の開発ではバックエンドを使わずフロントエンドのみで実装する必要がありました。

既存のReactコンポーネントをそのままPDF化したかったためです。本記事では、html2canvasとjsPDFを活用し、フロントエンドのみでPDF生成を実装した事例について紹介します。

背景

16type16type分布図

ミキワメ適性検査では16タイプマッピングという機能を提供しています。

上記のようにミキワメの性格検査の結果から16にタイプ分類をして、会社全体や部署ごとに性格特性の分布図を作成することが可能です。

チームビルディングなどに活用され好評を博している機能なのですが、ここにPDFダウンロード機能を追加しました。

課題


ミキワメ適性検査のフロントエンドはRailsのテンプレートエンジンであるhamlからReactへ移行中のため、その2つが混在している状況です。(2025/01 時点)

hamlからのPDF生成はすでに実装していましたが、Reactで作成された画面を直接PDF化した実績はありませんでした。

Reactで書かれたコンポーネントをPDF生成用にhamlで再作成し、従来通りバックエンドを介して実装することも検討しましたが工数がかかること、せっかくReactに移行したのにまたhaml書くんかいという気持ちがあり、もっと手軽にPDF生成する手段を模索することになりました。

技術選定


以下の条件で選定を進めました。

  • 導入コストが低い
  • 実装し易い
  • 主流ブラウザで動作可能
  • React Emotionが期待通り動作する

バックエンドを介さずPDF生成をしたいという需要は一定あると思うのですが、意外と選択肢が少なく、結論としてはhtml2canvasとjsPDFを組み合わせて採用することになりました。

html2canvasはHTML要素をキャプチャし画像化することが出来、jsPDFは画像からPDF生成が可能で、いずれもJavaScriptライブラリです。

react-pdfはJSXで直接レイアウトを記述できる点が魅力的でしたが、React v17までの対応に留まっており、ミキワメ適性検査が採用しているReact v18では動作しませんでした。また、Emotionの互換性もないため、今回の要件には適合しませんでした。

選定条件html2canvas + jsPDFreact-pdf
導入コストが低い✅ 導入が容易✅ 比較的容易
実装し易い✅ シンプルなAPI✅ JSXで直感的に記述可能
主流ブラウザで動作可能✅ Chrome, Firefox, Edge, Safari✅ Chrome, Firefox, Edge, Safari
React Emotionが動作✅ 影響なし❌ 互換性なし React v18に未対応
バックエンド不要でPDF生成✅ 可能✅ 可能
レイアウトの自由度⚠️ HTMLレンダリングに依存✅ JSXで柔軟に記述可能
PDFのカスタマイズ性✅ 縦横・サイズ指定可能✅ 詳細なスタイル調整可能

直面した課題


  • 一部のスタイルが反映されない
  • ブラウザ上でレンダリングが必要なため、表示の工夫が必要
  • 既存機能と一貫性のあるダウンロード操作を実装する必要がある

解決策


  • スタイルの問題は、デザイナーと相談し、許容範囲を判断
  • 一旦非表示エリアにPDF生成用コンポーネントを表示し、見えない形でPDFを生成
  • 既存のダウンロードフローと統一し、ユーザーのUXを維持

デモで使用した実装


シンプルですが以下でブラウザに表示させたPDF生成用コンポーネントをユーザが視認できないよう調整しました。

  1. ダウンロードボタン押下をトリガーにPDF生成用コンポーネントをブラウザに表示
  2. PDF生成、ダウンロード処理
  3. 完了後非表示にする

最終的な実装ではベースレイアウト外でPDF生成用コンポーネントを表示させ、視認のリスクを軽減させています。

export const PdfDownloadButton = ({ className }: Props) => {
const [showPDF, setShowPDF] = useState(false); // PDF生成用コンポーネントを表示する状態管理

// ボタンクリック時にPDF生成用コンポーネントを表示
const handleClick = () => {
setShowPDF(true);
};

// showPDF が true になったら PDF を生成し、完了後に元の状態に戻す
useEffect(() => {
if (showPDF) {
PdfDownload(); // PDF生成処理を実行
setShowPDF(false); // PDF生成が終わったら状態を元に戻す
}
}, [showPDF]);

return (
<div>
{/* PDFダウンロードボタン */}
<Button onClick={handleClick} className={className}>
PDFダウンロード
</Button>
{/* PDF生成用コンポーネント(表示されるとPDFを作成) */}
{showPDF && <PdfTargetComponent />}
</div>
);
};

// PDFを生成する関数
const PdfDownload = () => {
const target = document.getElementById('pdf-target'); // PDF対象の要素を取得
if (target === null) return;

// 取得した要素をPDFとして生成
generatePdf(target);
};

まとめ


フロントエンドのみでPDF生成を行う方法として、html2canvas + jsPDFは有力な選択肢のひとつです。特に、既存のバックエンドを改修せずにPDFを導入したい場合や、Reactで作成されたコンポーネントをそのままPDF化したい場合に有効です。一方で、スタイルの反映やブラウザレンダリングの工夫が必要になるため、導入前に許容範囲を整理することが重要です。