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