【React】コンポーネントをそのまま表も含めてPDFに変換する
こんにちは、フリーランスエンジニアの太田雅昭です。
jsPDF
jsPDFは、クライアントサイドでPDFを作成できるライブラリです。
htmlからも作成できるため、表などが簡単に作れます。
参考サイト
以下のサイトを参考にさせていただきました。
手順
フォントをセットアップする
以下より、フォントをjsに変換します。今回はNoto Sans JPを使用しました。
変換したjsを下記のようにインポートします。
import "./NotoSansJP-Regular-normal";
これでフォントセットアップ完了です。
コンポーネントを作る
再利用を考慮して、子要素全てをPDFにする形としました。
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
import { Box, Button } from "@mui/material";
import { jsPDF } from "jspdf";
import { useRef } from "react";
import "./NotoSansJP-Regular-normal";
const FONT = "NotoSansJP-Regular";
type Props = {
children: React.ReactNode;
}
export function PdfContainer(props: Props) {
const { children } = props;
const targetRef = useRef<HTMLDivElement>(null);
const pdfRef = useRef(new jsPDF());
function getFileName() {
const timestamp = new Date().getTime();
return `download_${timestamp}.pdf`;
};
function savePdf() {
if (!targetRef.current) return;
pdfRef.current.html(targetRef.current, {
callback(doc) {
const fileName = getFileName();
doc.setFont(FONT, "normal");
doc.setFontSize(12);
doc.save(fileName);
},
x: 15,
y: 15,
width: 170,
windowWidth: 775,
});
};
return <Box sx={{ border: "1px solid #aaa", padding: 2 }}>
<Button variant="contained" onClick={savePdf}>
<PictureAsPdfIcon fontSize='small' />
PDFを保存
</Button>
<Box sx={{ '& *': { fontFamily: FONT } }} ref={targetRef}>
{children}
</Box>
</Box>
};
試してみる
以下のコードで試してみます。ChatGPT出力をそのまま使っているため、重複が半端ないです。実際にはMuiのstyledなどを使ってスリムにできます。
export function PdfContainerSample() {
return <PdfContainer>
<h1>PDF Container Sample</h1>
<p>これはPDFコンテナのサンプルです。</p>
<table style={{ borderCollapse: 'collapse', width: '100%' }}>
<thead>
<tr>
<th style={{ border: '1px solid black', padding: '8px' }}>項目</th>
<th style={{ border: '1px solid black', padding: '8px' }}>説明</th>
<th style={{ border: '1px solid black', padding: '8px' }}>値</th>
</tr>
</thead>
<tbody>
<tr>
<td style={{ border: '1px solid black', padding: '8px' }}>サンプル1</td>
<td style={{ border: '1px solid black', padding: '8px' }}>これはサンプル1の説明です。</td>
<td style={{ border: '1px solid black', padding: '8px' }}>100</td>
</tr>
<tr>
<td style={{ border: '1px solid black', padding: '8px' }}>サンプル2</td>
<td style={{ border: '1px solid black', padding: '8px' }}>これはサンプル2の説明です。</td>
<td style={{ border: '1px solid black', padding: '8px' }}>200</td>
</tr>
<tr>
<td style={{ border: '1px solid black', padding: '8px' }}>サンプル3</td>
<td style={{ border: '1px solid black', padding: '8px' }}>これはサンプル3の説明です。</td>
<td style={{ border: '1px solid black', padding: '8px' }}>300</td>
</tr>
</tbody>
</table>
</PdfContainer>
}
結果以下のようになりました。
ブラウザ表示

PDF表示

ちょっとずれてますね。本番では細かな修正が必要そうです。