📚
ReactでPDFを作る
はじめに
ハッカソンで開発している際にPDFダウンロードする機能を作る話になり、Next.jsで作ることにしました
ただ時間が厳しかったため、かなりAI頼りになってしまいました
必要な要件
- ボタンを押したらPDFとしてダウンロードできる
- 中身はバックエンドにfetchしたものを入れる
- 全ての要素が表示の順序関係なく返される(json)
- できているデザインを実装する
使ったライブラリ
- html2canvas
- jsPDF
最初調べた時はreact-pdfを使おうとしましたが、直接PDFを作っていくものなので独自の記法なこともありスタイリングなどもうまくできなかったので、断念しました
そこで、DOM → PNG → PDFというようにそれぞれ変換するためのライブラリを使って実装しました!
実装
基本的な作りはこの記事を参考にしました!
グリッドレイアウトや順序バラバラに返されたデータをどうやるかなどは、Claudeに頼りました
ライブラリのインストール
npm install html2canvas jspdf
PDFにするコンポーネントを実装
実際にPDFにしたい中身をコンポーネントとして実装します!
16:9のスライドみたいな感じをイメージしています
PdfPartsというカード要素を縦2横4のグリッドレイアウトにしています
export const PDFContent = () => {
return (
<div
id="pdf-id"
className="w-[1200px] aspect-video py-16 px-8"
>
<h1>タイトル</h1>
<ul className="divide-[#4f4f4f] divide-y-4 flex-1 content-center">
<div className="col-span-4 row-span-1 grid grid-cols-4 gap-x-4 pb-14">
{contents.slice(0, 4).map((item, i) => (
<PdfParts
key={i}
color={item.color}
title={item.title}
content={item.content}
/>
))}
</div>
<div className="col-span-4 row-span-1 grid grid-cols-4 gap-x-4 pt-14">
{contents.slice(4, 8).map((item, i) => (
<PdfParts
key={i}
color={item.color}
title={item.title}
content={item.content}
/>
))}
</div>
</ul>
</div>
);
};
ボタンを押した時の関数
ダウンロードボタンを押した時に、html2canvasで画像にして、jsPDFでpdfファイルに変換する機能を実装する
この時、PDFのサイズを上手く指定しないとDOMに合ってくれません
const pdhDownloadHandler = () => {
// PDFファイルに変換したいコンポーネントのidを検索してDOM要素を取得する
const target = document.getElementById("pdf-id");
if (target === null) return;
html2canvas(target, { scale: 2.5 }).then((canvas) => {
const imgData = canvas.toDataURL("image/png", 1.0);
// A4横向きのPDFサイズ (単位: mm)
const slideWidth = 320; // スライドの幅
const slideHeight = 180; // スライドの高さ
// コンポーネント画像がA4ページに収まるようにスケーリング
const pdf = new jsPDF({
orientation: "landscape",
unit: "mm",
format: [slideWidth, slideHeight],
});
// A4ページ全体に収まるように、画像をページ全体にリサイズ
pdf.addImage(imgData, "PNG", 0, 0, slideWidth, slideHeight);
// PDFを保存
pdf.save(`judge-result.pdf`);
});
};
ダウンロードボタン
ページコンポーネントにボタン要素をおいて、さらに先ほど作ったPDFの中身もおきます
画面としては表示したくないのですが、document.getElementById("pdf-id")
で取得するので含める必要があります
透明度を0にして、画面幅が1200px以下になった際にもレイアウトが崩れないように実装しました
export const Page = () => {
return (
<>
<button>ダウンロード</button>
<div className="absolute top-0 left-0 w-[1200px] opacity-0 pointer-events-none" >
<PDFContent />
</div>
</>
);
}
まとめ
今回は時間がない中、PDFダウンロード機能を作った話でした
なかなか難しかったです
時間があればreact-pdfで構築できた方が良さそうです
Discussion