🐣

表示中の画面をワンクリックでPDFダウンロードする

2024/07/24に公開

こんにちは!株式会社ココナラのプロダクト開発部フロントエンド開発グループ所属の飯塚です。

ココナラでは、一定の条件を満たすと「納品書」等の帳票がダウンロードできます。
各帳票は、Webサイト上で確認したうえでワンクリックでPDFダウンロードできるようになっています。
以下は実際の納品書画面になります。

納品書のサンプル

今回は、その ワンクリックでPDFダウンロード の実装方法についてご紹介します!

要件

実装にあたり、要件は以下の通りでした。

  • ブラウザの「印刷」からPDFダウンロードすることは可能だが、手間なのでワンクリックでPDFダウンロードしたい
  • ヘッダーやフッターなど、帳票に関係ない要素はPDFダウンロード時に含めないようにしたい
  • パフォーマンス影響が小さい方法を取りたい

技術選定

表示中の画面をスクレイピングし、PDF化する案も検討したのですがサーバー負荷が高く断念しました。
またユーザーごとに事前にPDFを作成しておき、いつでもダウンロードできる案も検討しましたが、ダウンロードしないユーザーがいるとムダな処理になってしまうので断念しました。

色々調べていくうちに、「html2canvas」と「jsPDF」という2つのライブラリを組み合わせると実現できることがわかったのでそちらを今回は採用しました。

処理の流れ

処理の流れはシンプルです。

  1. html2canvasを使って指定のHTMLをCanvas変換する
  2. Canvasを画像に変換する。
  3. jsPDFを使って、画像化したファイルをPDF化する。
  4. PDFをダウンロードする

以上です!

実際のコードは下記のようになっています。

import html2canvas from 'html2canvas'
import { jsPDF } from 'jspdf'

const element = document.querySelector('#target');
html2canvas(element).then((canvasEl) => { // htmlをcanvasに変換
    const imgData = canvasEl.toDataURL('image/jpeg'); // canvasをjpegに変換

    const doc = new jsPDF()
    const { width } = doc.internal.pageSize // PDFの横幅を取得する
    doc.addImage(imgData, 'JPEG', 0, 0, width, 0) // PDFページの横幅いっぱいにjpeg画像を追加
    doc.save('ファイル名.pdf') // ダウンロード
});

注意点

CSSの記述には制限がある

html2canvasのロジックとしては、実際にスクリーンショットを取っているわけではなく、WebページのDOMやCSSを解釈してCanvasに変換しています。
なので、新しいCSSプロパティのパースに失敗したり、ブラウザ間で挙動が異なったりする可能性があります。
実装時には、細かくデバッグしつつ実装することをオススメします。

クロスオリジンの制限に引っかかることがある

html2canvasでCanvasに変換しようとするDOMの中に、外部ファイルを参照する記述があるとクロスオリジン制約に引っかかることがあります。
今回の実装では「ココナラロゴ」を帳票に含める必要がありましたが、そちらがクロスオリジン制約に引っかかりました。
解決策として、html2canvas対象のHTMLに画像リンクを設定するのではなく、Base64変換したココナラロゴをHTMLに埋め込むことで対応しました。

PDFダウンロード時のファイルサイズについて

Canvasから画像に変換するときに、png を選択し、そこからPDF化すると100MB以上のファイルとなってしまいました。
jpeg を選択してPDF化すると3MBくらいになりますので、ココナラでは jpeg 変換を行った後にPDF化をしております。
要件として帳票のように画質を細かく気にしなくて良いのであれば、jpeg で画像に変換するのも選択肢の1つかもしれません。

最後に

ここまで読んでいただき、ありがとうございました!
「html2canvas」と「jsPDF」の2つのライブラリをまとめた、「html2PDF」というものもあるので、サクッと実装したい方はこちらを利用すると早いかもしれないです。

ココナラではエンジニアを募集しています。
募集求人については下記のリンクからご確認ください。
https://coconala.co.jp/recruit/engineer

フロントエンドの求人はこちらです。
https://open.talentio.com/r/1/c/coconala/pages/49717

Discussion