はじめに
Webアプリでユーザーにマニュアルや請求書のPDFを見せたい、という場面って結構あるんですよね。でも、ブラウザによってはプラグインが必要だったり、外部ビューアに飛ばしたりと、なかなかスマートにいかない。
そこで使えるのが PDF.js というライブラリ。Mozillaが開発していて、GitHubスターは52,000超え。Firefoxに標準搭載されているPDFビューアの中身そのものです。
正直なところ、PDFをWeb上でまともに扱いたいならこれ一択ですね。
PDF.jsとは
PDF.jsは、HTML5とJavaScriptだけでPDFを解析・レンダリングするライブラリです。Web標準技術のみで動作するので、プラグインもFlashも不要。
基本情報:
| 項目 | 内容 |
|---|---|
| 開発元 | Mozilla |
| ライセンス | Apache 2.0 |
| GitHubスター | 52,400+ |
| コントリビューター | 407人 |
| 初回リリース | 約15年前 |
FirefoxユーザーならすでにこのPDF.jsを毎日使っているはず。ブラウザ内蔵のPDFビューアがまさにこれなんですよね。
特徴・メリット
1. プラグイン不要
Flash PlayerやAdobe Readerといった外部プラグインが一切必要ない。純粋なJavaScriptとCanvas/SVGだけで動作する。これ、意外と大きなメリットで、企業のセキュリティポリシーでプラグインが禁止されている環境でも問題なく使える。
2. クロスブラウザ対応
Chrome、Firefox、Safari、Edgeなど主要ブラウザで動作する。モダンブラウザ向けとレガシーブラウザ向けの2種類のビルドが用意されていて、IE11をサポートする必要がある場合も対応可能。
3. カスタマイズ性
ビューアのUIを自由にカスタマイズできる。ページ送り、ズーム、検索、印刷など、標準機能を組み合わせて独自のPDFビューアを作れる。
4. 大規模な実績
Firefoxに統合されているということは、数億人のユーザーが日常的に使っているということ。バグが少なく、互換性も高い。これは個人的に重要視しているポイント。
インストール方法
npmで簡単にインストールできる。
npm install pdfjs-dist
yarnの場合:
yarn add pdfjs-dist
CDNを使う場合:
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.mjs" type="module"></script>
基本的な使い方
シンプルにPDFを表示する
まずは最小構成でPDFを表示してみる。
<!DOCTYPE html>
<html>
<head>
<title>PDF.js サンプル</title>
</head>
<body>
<canvas id="pdf-canvas"></canvas>
<script type="module">
import * as pdfjsLib from 'pdfjs-dist';
// ワーカーの設定
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs';
// PDFを読み込む
const loadingTask = pdfjsLib.getDocument('/path/to/document.pdf');
loadingTask.promise.then(pdf => {
console.log('PDF読み込み完了: ' + pdf.numPages + 'ページ');
// 最初のページを取得
pdf.getPage(1).then(page => {
const scale = 1.5;
const viewport = page.getViewport({ scale });
const canvas = document.getElementById('pdf-canvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// ページをレンダリング
page.render({
canvasContext: context,
viewport: viewport
});
});
});
</script>
</body>
</html>
React/Next.jsでの使用例
Reactで使う場合はこんな感じになる。
'use client';
import { useEffect, useRef, useState } from 'react';
import * as pdfjsLib from 'pdfjs-dist';
// ワーカーの設定
pdfjsLib.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs`;
interface PDFViewerProps {
url: string;
}
export const PDFViewer = ({ url }: PDFViewerProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const [numPages, setNumPages] = useState(0);
const [currentPage, setCurrentPage] = useState(1);
const [pdfDoc, setPdfDoc] = useState<pdfjsLib.PDFDocumentProxy | null>(null);
useEffect(() => {
const loadPDF = async () => {
const pdf = await pdfjsLib.getDocument(url).promise;
setPdfDoc(pdf);
setNumPages(pdf.numPages);
};
loadPDF();
}, [url]);
useEffect(() => {
const renderPage = async () => {
if (!pdfDoc || !canvasRef.current) return;
const page = await pdfDoc.getPage(currentPage);
const scale = 1.5;
const viewport = page.getViewport({ scale });
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
if (!context) return;
canvas.height = viewport.height;
canvas.width = viewport.width;
await page.render({
canvasContext: context,
viewport: viewport,
}).promise;
};
renderPage();
}, [pdfDoc, currentPage]);
return (
<div className="pdf-viewer">
<div className="controls">
<button
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
disabled={currentPage <= 1}
>
前へ
</button>
<span>{currentPage} / {numPages}</span>
<button
onClick={() => setCurrentPage(p => Math.min(numPages, p + 1))}
disabled={currentPage >= numPages}
>
次へ
</button>
</div>
<canvas ref={canvasRef} />
</div>
);
};
テキスト抽出
PDFからテキストを抽出することも可能。OCRではなく、PDF内のテキストデータを直接取得できる。
const extractText = async (url: string): Promise<string> => {
const pdf = await pdfjsLib.getDocument(url).promise;
let fullText = '';
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const textContent = await page.getTextContent();
const pageText = textContent.items
.map((item: any) => item.str)
.join(' ');
fullText += pageText + '\n';
}
return fullText;
};
実践的なユースケース
請求書・見積書の表示
SaaSで請求書PDFをそのまま表示したいケース。ダウンロードリンクだけ置くより、その場で確認できたほうがUXは良い。
マニュアル・ドキュメントビューア
製品マニュアルをWebで公開する場合、PDF.jsを使えば目次機能や検索機能も実装できる。
PDFフォームの入力
PDF.jsはフォーム入力もサポートしている。ユーザーがブラウザ上でPDFフォームに入力し、そのデータを取得するといった使い方も可能。
ファイルアップロード時のプレビュー
ユーザーがPDFをアップロードする際、送信前にプレビュー表示したい場合にも使える。
const handleFileUpload = async (file: File) => {
const arrayBuffer = await file.arrayBuffer();
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
// プレビュー表示処理
};
パフォーマンスの考慮点
PDF.jsは優秀だが、大きなPDFを扱う場合はいくつか注意点がある。
- ページ単位でレンダリング: 100ページあるPDFを全部一度にレンダリングするのは避ける
- Web Worker: デフォルトでWorkerを使うので、メインスレッドをブロックしない
- メモリ管理: 不要になったページは
cleanup()で解放する
// 使い終わったらクリーンアップ
page.cleanup();
// ドキュメント全体を破棄
pdf.destroy();
まとめ
PDF.jsは、WebアプリでPDFを扱うなら真っ先に検討すべきライブラリですね。
- Mozillaが開発していて信頼性が高い
- プラグイン不要でどのブラウザでも動く
- カスタマイズ性が高い
- 52,000スター、407人のコントリビューターという実績
個人的には、PDFをWebで表示したいという要件が出てきたら、まずPDF.jsで実現できないか検討するようにしている。大抵のことはこれで解決する。
ちなみに、もっと高機能なビューアが必要なら、PDF.jsをベースにしたreact-pdfというラッパーライブラリもあるので、そちらも合わせてチェックしてみてください。