この記事では、Node.js、Puppeteer、ヘッドレス Chrome、Docker を使用して、複雑なスタイルの React ページから PDF ドキュメントを生成する方法を説明します。
関連する推奨事項: 「nodejs チュートリアル 」
背景: 数か月前、お客様から、ユーザーが React を利用できるようにする関数を開発してほしいとの依頼がありました。 PDF 形式のページコンテンツ。このページは基本的に患者症例のレポートとデータ視覚化であり、多くの SVG が含まれています。レイアウトを操作したり、HTML 要素の再配置を実行したりするための特別なリクエストもあります。したがって、元の React ページと比較して、PDF には異なるスタイルと追加コンテンツが存在するはずです。
このタスクは単純な CSS ルールで解決するよりもはるかに複雑であるため、最初にそれを実現する可能な方法を検討します。私たちは 3 つの主な解決策を見つけました。このブログ投稿では、その可能性と最終的な実装について説明します。
最後のオプション 3: Node.js、Puppeteer、ヘッドレス Chrome
PDF ファイルはクライアント側とサーバー側の両方で生成できます。ただし、ユーザーのブラウザが提供できるすべてのリソースを使い果たしたくないため、バックエンドに処理させる方がおそらく合理的です。
それでも、両方の方法の解決策を示します。
一見すると、この解決策が最も簡単であるように見えます。実際にそうであることがわかりますが、独自の解決策があります。制限。これは、PDF 内のテキストを選択したり、テキストの検索を実行したりするなど、特別なニーズがない場合に使いやすい方法です。
この方法はシンプルで簡単です。ページからスクリーンショットを作成し、それを PDF ファイルに配置します。とても簡単です。これを実現するには、2 つのパッケージを使用できます。
#コーディングを開始します:
npm install html2canvas jspdf
import html2canvas from 'html2canvas' import jsPdf from 'jspdf' function printPDF () { const domElement = document.getElementById('your-id') html2canvas(domElement, { onclone: (document) => { document.getElementById('print-button').style.visibility = 'hidden' }}) .then((canvas) => { const img = canvas.toDataURL('image/png') const pdf = new jsPdf() pdf.addImage(imgData, 'JPEG', 0, 0, width, height) pdf.save('your-filename.pdf') })
以上です。
html2canvas
の onclone
メソッドに注意してください。スクリーンショットを撮る前に DOM を操作する必要がある場合 (印刷ボタンを非表示にするなど)、非常に便利です。このパッケージを使用する多くのプロジェクトを見てきました。しかし、残念ながら、PDF の作成をバックエンドで行う必要があるため、これは私たちが望んでいることではありません。
NPM では、jsPDF (前述) や PDFKit など、いくつかのライブラリが利用可能です。問題は、これらのライブラリを使用したい場合、ページを再構築する必要があることです。その後のすべての変更を PDF テンプレートと React ページの両方に適用する必要があるため、これは明らかに保守性に悪影響を及ぼします。
以下のコードを参照してください。 PDF ドキュメントを自分で手動で作成する必要があります。 DOM を調べて各要素を見つけて PDF 形式に変換する必要がありますが、これは面倒な作業です。もっと簡単な方法を見つけなければなりません。
doc = new PDFDocument doc.pipe fs.createWriteStream('output.pdf') doc.font('fonts/PalatinoBold.ttf') .fontSize(25) .text('Some text with an embedded font!', 100, 100) doc.image('path/to/image.png', { fit: [250, 300], align: 'center', valign: 'center' }); doc.addPage() .fontSize(25) .text('Here is some vector graphics...', 100, 100) doc.end()
このコード スニペットは PDFKit ドキュメントから引用しています。ただし、既存の (そして常に変化する) HTML ページを変換するのではなく、PDF ファイルを直接生成することが目標の場合には、依然として便利です。
Puppeteer とは何ですか?そのドキュメントには次のように書かれています:
Puppeteer は、DevTools プロトコルで Chrome または Chromium を制御するための高レベル API を提供するノード ライブラリです。 Puppeteer はデフォルトで Chrome または Chromium をヘッドレス モードで実行しますが、フル (非ヘッドレス) モードで実行するように構成することもできます。
これは基本的に、Node.js から実行できるブラウザです。ドキュメントを読むと、最初に Puppeteer を使用してページの スクリーンショットと PDF を生成できることが記載されています。素晴らしい!これはまさに私たちが望んでいることです。
最初に npmi i puppeteer
を使用して Puppeteer をインストールし、機能を実装します。
const puppeteer = require('puppeteer') async function printPDF() { const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); await page.goto('https://blog.risingstack.com', {waitUntil: 'networkidle0'}); const pdf = await page.pdf({ format: 'A4' }); await browser.close(); return pdf })
これは、URL に移動してサイトの PDF ファイルを生成する単純な関数です。
まず、ブラウザを起動し (PDF 生成はヘッドレス モードでのみサポートされます)、新しいページを開いてビューポートを設定し、指定された URL に移動します。
设置 waitUntil:'networkidle0'
选项意味着当至少500毫秒没有网络连接时,Puppeteer 会认为导航已完成。 (可以从 API docs 获取更多信息。)
之后,我们将 PDF 保存为变量,关闭浏览器并返回 PDF。
注意:page.pdf
方法接收 options
对象,你可以使用 'path' 选项将文件保存到磁盘。如果未提供路径,则 PDF 将不会被保存到磁盘,而是会得到缓冲区。(稍后我将讨论如何处理它。)
如果需要先登录才能从受保护的页面生成 PDF,首先你要导航到登录页面,检查表单元素的 ID 或名称,填写它们,然后提交表单:
await page.type('#email', process.env.PDF_USER) await page.type('#password', process.env.PDF_PASSWORD) await page.click('#submit')
要始终将登录凭据保存在环境变量中,不要硬编码!
Puppeteer 也有这种样式操作的解决方案。你可以在生成 PDF 之前插入样式标记,Puppeteer 将生成具有已修改样式的文件。
await page.addStyleTag({ content: '.nav { display: none} .navbar { border: 0px} #print-button {display: none}' })
好的,现在你已经在后端生成了一个 PDF 文件。接下来做什么?
如上所述,如果你不把文件保存到磁盘,将会得到一个缓冲区。你只需要把含有适当内容类型的缓冲区发送到前端即可。
printPDF.then(pdf => { res.set({ 'Content-Type': 'application/pdf', 'Content-Length': pdf.length }) res.send(pdf)
现在,你只需在浏览器向服务器发送请求即可得到生成的 PDF。
function getPDF() { return axios.get(`${API_URL}/your-pdf-endpoint`, { responseType: 'arraybuffer', headers: { 'Accept': 'application/pdf' } })
一旦发送了请求,缓冲区的内容就应该开始下载了。最后一步是将缓冲区数据转换为 PDF 文件。
savePDF = () => { this.openModal(‘Loading…’) // open modal return getPDF() // API call .then((response) => { const blob = new Blob([response.data], {type: 'application/pdf'}) const link = document.createElement('a') link.href = window.URL.createObjectURL(blob) link.download = `your-file-name.pdf` link.click() this.closeModal() // close modal }) .catch(err => /** error handling **/) } <button onClick={this.savePDF}>Save as PDF</button>
就这样!如果单击“保存”按钮,那么浏览器将会保存 PDF。
我认为这是实施中最棘手的部分 —— 所以让我帮你节省几个小时的百度时间。
官方文档指出“在 Docker 中使用 headless Chrome 并使其运行起来可能会非常棘手”。官方文档有疑难解答部分,你可以找到有关用 Docker 安装 puppeteer 的所有必要信息。
如果你在 Alpine 镜像上安装 Puppeteer,请确保在看到页面的这一部分时再向下滚动一点。否则你可能会忽略一个事实:你无法运行最新的 Puppeteer 版本,并且你还需要用一个标记禁用 shm :
const browser = await puppeteer.launch({ headless: true, args: ['--disable-dev-shm-usage'] });
否则,Puppeteer 子进程可能会在正常启动之前耗尽内存。
可能有人认为从开发人员的角度来看,简单地使用 CSS 打印规则很容易。没有 NPM 模块,只有纯 CSS。但是在跨浏览器兼容性方面,它的表现如何呢?
在选择 CSS 打印规则时,你必须在每个浏览器中测试结果,以确保它提供的布局是相同的,并且它不是100%能做到这一点。
例如,在给定元素后面插入一个 break-after 并不是一个多么高深的技术,但是你可能会惊讶的发现要在 Firefox 中使用它需要使用变通方法。
除非你是一位经验丰富的 CSS 大师,在创建可打印页面方面有很多的经验,否则这可能会非常耗时。
如果你可以使打印样式表保持简单,打印规则是很好用的。
让我们来看一个例子吧。
@media print { .print-button { display: none; } .content div { break-after: always; } }
上面的 CSS 隐藏了打印按钮,并在每个 div
之后插入一个分页符,其中包含content
类。有一篇很棒的文章总结了你可以用打印规则做什么,以及它们有什么问题,包括浏览器兼容性。
考虑到所有因素,如果你想从不那么复杂的页面生成 PDF,CSS打印规则非常有效。
让我们快速回顾前面介绍的方案,以便从 HTML 页面生成 PDF 文件:
印刷をお楽しみください。
英語の元のアドレス: https://blog.risingstack.com/pdf-from-html-node-js-puppeteer/
プログラミング関連の知識をさらに深めてください。訪問: プログラミング入門! !
以上がHTMLをPDFに変換するにはどうすればよいですか?手法の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。