PDF 為世界提供了一種在各種設(shè)備之間高度兼容的共享文檔和媒體的通用格式,但以編程方式生成它們通常很棘手。
我們將探索如何在不同環(huán)境中使用 JavaScript 生成 PDF 的一些選項(xiàng)。
生成 PDF 的麻煩之處……
在使用 PDF 時(shí),您通常會(huì)像閱讀圖像一樣閱讀或查看它們,但如果您曾經(jīng)嘗試復(fù)制某些文本、搜索 PDF 或單擊鏈接,您可能已經(jīng)注意到 PDF 不僅僅是一張靜態(tài)圖像。
許多生成 PDF 的解決方案依賴于將其生成為圖像,但缺乏嵌入文本所提供的可訪問性和可用性。
但根據(jù)您的限制和環(huán)境,也許值得權(quán)衡。
html2pdf.js
html2pdf.js是一個(gè)客戶端庫(kù),允許您使用 Canvas(特別是html2canvas和jsPDF)從 HTML 呈現(xiàn) PDF 。
要?jiǎng)?chuàng)建 PDF,您需要指定要從頁面呈現(xiàn)的元素,將其傳遞到 html2pdf 實(shí)例中,然后庫(kù)將生成 PDF 并提示用戶下載它。
如何開始?
您可以通過幾種不同的方式包含 html2pdf.js,包括指向第三方 CDN 的腳本標(biāo)簽或通過 npm 導(dǎo)入它。
如果使用 npm,首先安裝 html2pdf.js:
npm install html2pdf.js
將其作為依賴項(xiàng)導(dǎo)入:
import html2pdf from 'html2pdf.js';
選擇一個(gè) HTML 元素并將其傳遞給 html2pdf:
html2pdf(document.getElementById('my-id'));
這將提示您的用戶開始下載文件。
您還可以在支持的地方動(dòng)態(tài)導(dǎo)入依賴項(xiàng),以避免將其直接與您的應(yīng)用程序捆綁在一起并僅在需要時(shí)加載它。
const html2pdf = await require('html2pdf.js');
html2pdf(document.getElementById('my-id'));
文檔中有很多可用的選項(xiàng)。
例如,在我顯示發(fā)票的頁面上,html2pdf.js 將呈現(xiàn):
它有什么好處?
html2pdf 允許您使用 JavaScript 直接在瀏覽器中生成 PDF。這意味著您無需向外部服務(wù)器發(fā)出請(qǐng)求,因此您需要處理的基礎(chǔ)設(shè)施和網(wǎng)絡(luò)請(qǐng)求更少。
您還可以使用任何工具以任何您想要的方式管理您的 HTML,因?yàn)樽罱K您將傳遞一個(gè) DOM 節(jié)點(diǎn)來呈現(xiàn)。
這也提高了您已經(jīng)構(gòu)建的頁面的可重用性,因此您不必為 UI 和 PDF 版本分別維護(hù)多個(gè)頁面。
還有什么更好的呢?
總體來說,它運(yùn)行良好,但在生成目標(biāo) HTML 時(shí),渲染可能會(huì)有點(diǎn)不一致。CSS 可能不太完美,有時(shí)頁面的某些部分可能會(huì)出現(xiàn)偏移或被切斷。
雖然 API 為您提供了一些選項(xiàng)和靈活性,但您仍然受到頁面呈現(xiàn)方式的限制。您可以隱藏內(nèi)容 ( data-html2canvas-ignore
),但您無法提供特定樣式,例如,您可以像打印預(yù)覽那樣提供特定樣式。
頁面移動(dòng)導(dǎo)致項(xiàng)目被切斷的問題似乎可以通過在 html2pdf.js GitHub Issues中找到的一些基本樣式來修復(fù)。
@layer base { img { display: initial; } }
PDF Kit 和 React PDF
PDF Kit是另一個(gè) JavaScript HTML 到 PDF 渲染工具,其工作方式略有不同。
PDF Kit 本身實(shí)際上并不呈現(xiàn) HTML,但允許您使用它所描述的類似 HTML5 Canvas 的 API 來創(chuàng)建和定位元素。
但是在 React 環(huán)境中工作,你會(huì)得到更接近 HTML 或 JSX 的東西,其中React PDF使用組件 API 和 PDF Kit 來提供一種更自然的方式來表達(dá)內(nèi)容(就像在 React 中一樣)。
在這里我們將重點(diǎn)介紹 React PDF,但如果您想要純 JavaScript 版本,請(qǐng)查看文檔。
如何開始?
首先使用 npm 安裝 React PDF:
npm install @react-pdf/renderer
導(dǎo)入一些組件作為依賴項(xiàng):
import { renderToStream, Page, Text, Document, StyleSheet } from '@react-pdf/renderer';
設(shè)置一些樣式:
const styles = StyleSheet.create({
page: {
padding: 50
},
title: {
fontSize: 22,
},
});
創(chuàng)建文檔組件:
const MyDocument = () => (
<Document>
<Page style={styles.page}>
<Text style={styles.title}>My Text</Text>
</Page>
</Document>
);
如果渲染到流,請(qǐng)使用該renderToStream
方法:
await renderToStream(<MyDocument />)
此時(shí)它就像一個(gè) React 組件,因此您可以設(shè)置動(dòng)態(tài)值以便更輕松地處理動(dòng)態(tài)數(shù)據(jù)。
創(chuàng)建動(dòng)態(tài)路由器處理程序
使用它的一個(gè)示例是創(chuàng)建一個(gè) Next.js 路由處理程序,它像頁面一樣查詢數(shù)據(jù)并呈現(xiàn) PDF,并將其作為流返回。
為此,您可以在 創(chuàng)建路線app/pdf/route.tsx
,在其中,您將創(chuàng)建 PDF 組件、渲染它,并在 Next.js 響應(yīng)中返回它。
例如:
import { NextResponse } from 'next/server';
const Invoice = (props: InvoiceProps) => (
<Document>{/** PDF Components */}</Document>
);
export async function GET(request: Request, { params }: { params: { invoiceId: string; }}) {
const invoice = await getMyInvoice(params.invoiceId);
const stream = await renderToStream(<Invoice {...invoice} />)
return new NextResponse(stream as unknown as ReadableStream)
}
這可能導(dǎo)致:
它有什么好處?
渲染效果非常好。
您可以選擇如何呈現(xiàn)文檔,包括在視圖中呈現(xiàn)它以及將其呈現(xiàn)到 ReadableStream 中。
如果您想要?jiǎng)?chuàng)建一個(gè)可動(dòng)態(tài)生成和傳送 PDF 的路由器處理程序,或者想要在服務(wù)器上生成 PDF,那么這會(huì)非常方便,就像上面的例子一樣。
它還允許您嵌入實(shí)際字體。打開 PDF 后,文本節(jié)點(diǎn)實(shí)際上是可選擇和可復(fù)制的,這出于多種原因非常有用。
還有什么更好的呢?
目前,React PDF 似乎無法在即將推出的 React 19 中使用。GitHub Issues 中有一些關(guān)于如何支持(甚至是分叉)的討論。
雖然您能夠以類似 HTML/JSX 的結(jié)構(gòu)創(chuàng)建 PDF,但您仍然必須使用其組件單獨(dú)維護(hù)它,但如果不使用像 html2pdf 這樣的直接從 DOM 中抓取它的解決方案,該語法可能比其他一些 API 更好。
Puppeteer
Puppeteer 是一種流行的選項(xiàng),用于處理 HTML、截屏以及嘗試渲染通常涉及瀏覽器的動(dòng)態(tài)內(nèi)容。
這是我們可以用來根據(jù)現(xiàn)有內(nèi)容創(chuàng)建 PDF 的另一個(gè)工具。
其工作方式是 Puppeteer 幫助自動(dòng)化 Chromium 瀏覽器,一旦連接,我們就可以導(dǎo)航到不同的頁面,與這些頁面進(jìn)行交互,并且可以想象,截取屏幕截圖甚至生成 PDF。
如何開始?
使用 Puppeteer 非常依賴于您所處的環(huán)境。例如,在無服務(wù)器函數(shù)中運(yùn)行 Puppeteer 很棘手,但幸運(yùn)的是我有一個(gè)教程:使用 Puppeteer 和 Next.js API 路由構(gòu)建 Web Scraper。
對(duì)于這個(gè)例子,我假設(shè)您能夠運(yùn)行標(biāo)準(zhǔn) Puppeteer 庫(kù)。
首先,安裝 Puppeteer:
npm install puppeteer
將模塊導(dǎo)入到您的項(xiàng)目中:
import puppeteer from 'puppeteer';
然后我們可以自動(dòng)運(yùn)行 Puppeteer,例如,如果我們想要生成 PDF,我們可以運(yùn)行:
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://spacejelly.dev');
const pdf = await page.pdf();
await browser.close();
此時(shí),Uint8Array
我們可以將 PDF 上傳到我們選擇的位置。
例如:
?
提示:查看我的YouTube 視頻,我將在其中向您展示如何使用 Clerk 創(chuàng)建經(jīng)過身份驗(yàn)證的 Puppeteer 會(huì)話以生成 PDF!
或者,您也可以截取屏幕截圖,而不是生成 PDF:
const screenshot = await page.screenshot();
不同之處在于.screenshot
生成圖像而不是 PDF 文件,這是一個(gè)重要的區(qū)別。
Alt:使用 Puppeteer 生成的 PDF
它有什么好處?
Puppeteer 用途非常廣泛。你可以通過多種方式來控制頁面并與之交互。
設(shè)置好頁面后,您可以使用.pdf
包含嵌入文本的方法輕松捕獲它,這對(duì)于高質(zhì)量 PDF 至關(guān)重要。
還有什么更好的呢?
對(duì)于這種用例來說,Puppeteer 可能比較慢,但其他方法速度更快,可以帶來更好的用戶體驗(yàn)。
在環(huán)境中進(jìn)行設(shè)置也可能很棘手,假設(shè)您有一個(gè)可以設(shè)置 Puppeteer 的環(huán)境。
我們最好的選擇是什么?
它們都有各自的優(yōu)點(diǎn)和缺點(diǎn),但我認(rèn)為 React PDF 是我們更好的解決方案。
當(dāng)然,我們必須創(chuàng)建和維護(hù)一個(gè)單獨(dú)的模板,但該模板不需要與 UI 完全相同,對(duì)于交互性有不同的標(biāo)準(zhǔn)和期望級(jí)別。
與 html2pdf 相比,React PDF 為我們提供了嵌入文本。
與 Puppeteer 相比,React PDF 速度更快,設(shè)置也更容易。
該文章在 2024/10/28 16:27:22 編輯過