React 中的交互式 PDF 查看:完整教程
在现代 Web 开发中,我们经常需要在应用程序内直接展示 PDF 文档,而不是简单地提供一个下载链接。一个功能完善的 PDF 查看器不仅能渲染文档,还应该提供页面导航、缩放、旋转等交互功能,以提升用户体验。
本教程将详细介绍如何使用 react-pdf 这个强大的库,在 React 应用中从零开始构建一个功能丰富的交互式 PDF 查看器。
为什么选择 react-pdf?
从头开始解析和渲染 PDF 是一项极其复杂的任务。幸运的是,开源社区已经为我们提供了成熟的解决方案。react-pdf 是一个广受欢迎的 React 封装库,它基于 Mozilla 开发的 pdf.js,提供了简单易用的 React 组件来处理 PDF。
主要优点:
* 易于集成: 以 React 组件的形式提供,与现有项目无缝集成。
* 功能强大: 支持分页、缩放、文本层、注释层等高级功能。
* 社区活跃: 拥有庞大的用户群体和持续的维护。
* 高度可定制: 你可以完全控制查看器的外观和行为。
准备工作
在开始之前,请确保你已经安装了 Node.js 和 npm/yarn。我们将使用 Vite 创建一个新的 React 项目。
1. 创建 React 项目
打开终端,运行以下命令:
“`bash
使用 Vite 创建一个新的 React + TypeScript 项目
npm create vite@latest my-pdf-viewer — –template react-ts
进入项目目录
cd my-pdf-viewer
安装依赖
npm install
“`
2. 安装 react-pdf
接下来,将 react-pdf 添加到你的项目中:
bash
npm install react-pdf
3. 准备一个 PDF 文件
为了进行测试,将一个示例 PDF 文件(例如 sample.pdf)放入项目的 public 文件夹中。这样我们可以通过 URL /sample.pdf 来访问它。
步骤一:基础 PDF 显示
首先,我们来实现最基本的功能:加载并显示 PDF 的第一页。
react-pdf 的工作方式依赖于一个 “worker”。我们需要告诉它从哪里加载这个 worker 文件。react-pdf 会自动从 node_modules 中提供这个文件。
修改你的 src/App.tsx 文件,内容如下:
“`tsx
import { useState }.tsx’;
import { Document, Page, pdfjs } from ‘react-pdf’;
import ‘react-pdf/dist/esm/Page/AnnotationLayer.css’;
import ‘react-pdf/dist/esm/Page/TextLayer.css’;
// 设置 PDF.js worker 的路径
pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js;
const PDFViewer = () => {
const [numPages, setNumPages] = useState
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
}
return (
Page {pageNumber} of {numPages}
);
};
export default PDFViewer;
“`
代码解析:
1. 我们从 react-pdf 导入 Document 和 Page 组件,以及 pdfjs 对象。
2. 关键步骤:我们通过 pdfjs.GlobalWorkerOptions.workerSrc 设置了 worker 的路径。这里我们使用了 unpkg CDN 来快速获取对应版本的 worker 文件,这是最简单可靠的方式。
3. 我们还导入了两个 CSS 文件,用于正确显示 PDF 的文本层和注释层。
4. <Document> 组件负责加载 PDF 文件。file 属性指向我们的 PDF 文件。onLoadSuccess 是一个回调函数,在 PDF 加载成功后触发,返回一个包含 numPages(总页数)的对象。
5. <Page> 组件用于渲染单页。pageNumber 属性指定了要显示的页码。
6. 我们使用 useState 来存储总页数和当前页码。
现在运行你的应用 (npm run dev),你应该能看到 sample.pdf 的第一页被成功渲染出来了。
步骤二:添加页面导航
一个只能看第一页的查看器是不完整的。接下来,我们添加“上一页”和“下一页”的控制功能。
我们来创建一个控制工具栏,并添加导航按钮。
“`tsx
import { useState } from ‘react’;
import { Document, Page, pdfjs } from ‘react-pdf’;
import ‘react-pdf/dist/esm/Page/AnnotationLayer.css’;
import ‘react-pdf/dist/esm/Page/TextLayer.css’;
pdfjs.GlobalWorkerOptions.workerSrc = //unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.js;
const PDFViewer = () => {
const [numPages, setNumPages] = useState
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
setPageNumber(1); // 加载新文档时,重置到第一页
}
function goToPrevPage() {
setPageNumber((prevPageNumber) => Math.max(prevPageNumber – 1, 1));
}
function goToNextPage() {
if (numPages) {
setPageNumber((prevPageNumber) => Math.min(prevPageNumber + 1, numPages));
}
}
return (
上一页
第 {pageNumber} 页 / 共 {numPages ?? ‘–‘} 页
{/* PDF 显示区域 */}
<div style={{ border: '1px solid #ccc', width: '80%', height: '80vh', overflow: 'auto' }}>
<Document
file="/sample.pdf"
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
</div>
</div>
);
};
export default PDFViewer;
``goToPrevPage
**新功能解析:**
1. 我们创建了和goToNextPage函数来更新pageNumber状态。disabled
2. 我们添加了两个按钮,并使用属性来防止用户导航到无效的页码(例如小于1或大于总页数)。div` 中。
3. 为了更好的视觉效果,我们将 PDF 包裹在一个有边框和可滚动的
步骤三:实现缩放功能
缩放是 PDF 查看器不可或缺的功能。react-pdf 的 <Page> 组件提供了一个 scale 属性来控制渲染比例。
让我们在工具栏中加入缩放按钮。
“`tsx
// … (imports and worker setup remain the same)
const PDFViewer = () => {
const [numPages, setNumPages] = useState
const [pageNumber, setPageNumber] = useState(1);
const [scale, setScale] = useState(1.0); // 新增 state 用于控制缩放
// … (onDocumentLoadSuccess, goToPrevPage, goToNextPage functions remain the same)
function zoomIn() {
setScale(prevScale => prevScale + 0.1);
}
function zoomOut() {
setScale(prevScale => Math.max(prevScale – 0.1, 0.5)); // 最小缩放比例为 0.5
}
return (
上一页
第 {pageNumber} 页 / 共 {numPages ?? ‘–‘} 页
<div style={{ marginLeft: '20px' }}>
<button onClick={zoomOut}>-</button>
<span style={{ margin: '0 10px' }}>{(scale * 100).toFixed(0)}%</span>
<button onClick={zoomIn}>+</button>
</div>
</div>
{/* PDF 显示区域 */}
<div style={{ border: '1px solid #ccc', width: '80%', height: '80vh', overflow: 'auto' }}>
<Document
file="/sample.pdf"
onLoadSuccess={onDocumentLoadSuccess}
>
<Page
pageNumber={pageNumber}
scale={scale} // 应用缩放
/>
</Document>
</div>
</div>
);
};
export default PDFViewer;
“`
新功能解析:
1. 我们添加了一个新的 scale state,初始值为 1.0 (即 100%)。
2. 创建了 zoomIn 和 zoomOut 函数来增加或减少 scale 值。
3. 在工具栏中添加了对应的 “+” 和 “-” 按钮,并显示当前的缩放百分比。
4. 最重要的一步:将 scale state 作为 prop 传递给 <Page> 组件。
现在,你的 PDF 查看器已经具备了核心的交互能力!
步骤四:渲染所有页面(缩略图模式)
有时,用户可能希望一次性看到所有页面。我们可以通过循环来渲染所有的 <Page> 组件。
注意: 一次性渲染大量页面可能会消耗很多内存和性能。对于非常大的 PDF,这种方法需要谨慎使用或进行优化(例如,使用虚拟滚动)。
下面是一个渲染所有页面的简单示例:
“`tsx
// … (imports)
const PDFAllPagesViewer = () => {
const [numPages, setNumPages] = useState
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages);
}
return (
{Array.from(new Array(numPages ?? 0), (el, index) => (
pageNumber={index + 1}
scale={0.5} // 以较小的比例显示
renderTextLayer={false} // 关闭文本层以提高性能
renderAnnotationLayer={false} // 关闭注释层
/>
))}
);
};
“`
这个组件会以 50% 的比例渲染文档中的每一页,非常适合用作缩略图预览。
结论
通过本教程,我们使用 react-pdf 成功构建了一个功能齐全的交互式 PDF 查看器。我们实现了以下核心功能:
* 加载并渲染 PDF 文档。
* 实现上一页/下一页的页面导航。
* 实现放大/缩小的视图缩放。
react-pdf 还提供了更多高级功能,例如:
* 文本层(Text Layer):允许用户选择和复制 PDF 中的文本。我们在第一个例子中导入的 CSS (TextLayer.css) 就是为此服务的。
* 自定义加载动画:在 <Document> 组件加载时,你可以提供一个 loading prop 来显示自定义的加载指示器。
* 密码保护的 PDF:通过 onPassword 回调来处理需要密码的文档。
* 旋转页面:<Page> 组件接受一个 rotate prop (值为 90, 180, 270)。
要了解更多信息,请务必查阅 react-pdf 官方文档。希望这篇教程能帮助你轻松地在 React 项目中集成强大的 PDF 功能!