在 Web 应用中,直接预览和展示 PDF 文件是一项非常普遍的需求,例如在线查看发票、合同、电子书或用户上传的文档。react-pdf
就是解决这一问题的优秀库,它能让你在 React 应用中轻松地渲染和控制 PDF 文档的显示。
本文将作为一份详细的入门指南,带你从零开始,快速掌握 react-pdf
的核心用法,并配置常见功能,如分页、缩放等。
目录
- [什么是 react-pdf?](#什么是 react-pdf? "#1-%E4%BB%80%E4%B9%88%E6%98%AF-react-pdf")
- [快速上手:展示你的第一个 PDF](#快速上手:展示你的第一个 PDF "#2-%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E5%B1%95%E7%A4%BA%E4%BD%A0%E7%9A%84%E7%AC%AC%E4%B8%80%E4%B8%AA-pdf")
- 核心组件详解
- 常见功能配置
- 实现分页控制(上一页/下一页)
- [控制 PDF 尺寸与缩放](#控制 PDF 尺寸与缩放 "#%E6%8E%A7%E5%88%B6-pdf-%E5%B0%BA%E5%AF%B8%E4%B8%8E%E7%BC%A9%E6%94%BE")
- [加载不同来源的 PDF](#加载不同来源的 PDF "#%E5%8A%A0%E8%BD%BD%E4%B8%8D%E5%90%8C%E6%9D%A5%E6%BA%90%E7%9A%84-pdf")
- 开启文本选择与注释层
- 常见问题与最佳实践
- [错误:"Failed to load PDF worker"](#错误:"Failed to load PDF worker" "#%E9%94%99%E8%AF%AFfailed-to-load-pdf-worker")
- 性能优化
- 总结
1. 什么是 react-pdf?
react-pdf
是一个 React 库,专门用于在浏览器中显示和渲染 已存在的 PDF 文件。它底层使用了 Mozilla 开发的著名库 PDF.js
,并将其封装成易于在 React 中使用的组件。
它不是用来做什么的?(重要区别)
初学者最容易混淆的一点是 react-pdf
和 @react-pdf/renderer
的区别:
react-pdf
(本文主角): 用于展示 一个已有的.pdf
文件,就像一个 PDF 阅读器。@react-pdf/renderer
: 用于使用 React 组件从零创建 一个新的.pdf
文件。
一句话总结 :如果你想在网页上看 PDF,用
react-pdf
。如果你想生成 PDF,用@react-pdf/renderer
。
核心优势
- 与 React 生态无缝集成:使用声明式的组件语法,易于上手。
- 功能强大:支持分页、缩放、文本选择、链接跳转、注释层等高级功能。
- 高度可定制:你可以完全自定义 PDF 周边的 UI,如分页控制器、工具栏等。
- 性能优异:通过 Web Worker 在后台线程处理繁重的 PDF 解析工作,不阻塞主线程。
2. 快速上手:展示你的第一个 PDF
让我们通过一个简单的例子,看看在你的应用中嵌入一个 PDF 预览是多么容易。
环境准备
确保你已有一个 React 项目(例如通过 create-react-app
或 Vite
创建)。
bash
npx create-react-app my-pdf-viewer
cd my-pdf-viewer
安装依赖
在你的 React 项目中安装 react-pdf
。
bash
npm install react-pdf
# 或者
yarn add react-pdf
配置 PDF.js Worker(关键步骤)
react-pdf
使用 PDF.js
的 "worker" 来在后台解析 PDF,以避免卡顿。你需要告诉 react-pdf
在哪里找到这个 worker 文件。这是最容易出错的一步,但配置起来很简单。
在你的应用入口文件(例如 src/index.js
或 src/App.js
)的顶部添加以下代码:
jsx
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.js',
import.meta.url,
).toString();
注意 : 上述
import.meta.url
的语法适用于现代构建工具,如 Vite 和 Create React App 5+。对于旧版 CRA,你可能需要使用copy-webpack-plugin
来手动复制 worker 文件(详见文末的常见问题部分)。
编写显示组件
现在,创建一个组件来显示 PDF。我们先从显示 PDF 的第一页开始。
-
准备一个 PDF 文件。你可以从网上下载一个示例 PDF,或者使用自己的文件。为了简单起见,将它放在
public
文件夹下,例如public/sample.pdf
。 -
创建
src/PdfViewer.js
文件:
jsx
// src/PdfViewer.js
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
function PdfViewer() {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
}
return (
<div>
<Document
file="/sample.pdf" // public 文件夹下的文件
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
);
}
export default PdfViewer;
重要: 为了让文本选择和注释正常显示,你需要引入附带的 CSS 文件。
- 在
src/App.js
中使用它:
jsx
// src/App.js
import React from 'react';
import PdfViewer from './PdfViewer';
function App() {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
<PdfViewer />
</div>
);
}
export default App;
启动你的应用 (npm start
),你就能在页面上看到 sample.pdf
的第一页了!
3. 核心组件详解
react-pdf
的 API 非常简洁,主要围绕两个核心组件。
<Document>
这是加载 PDF 的容器组件。它负责获取和解析 PDF 文件,但不直接渲染任何内容。
file
属性 (必需): 指定 PDF 文件的来源。可以是 URL 字符串、文件对象(来自<input type="file">
)、或 Base64 编码的字符串。onLoadSuccess
属性 : 当 PDF 加载并解析成功时触发的回调函数。它会返回一个包含numPages
(总页数) 的对象,这是实现分页功能的关键。onLoadError
属性 : 加载失败时的回调,参数是error
对象。options
属性 : 传递给底层PDF.js
的高级选项,例如设置 HTTP 请求头。
<Page>
该组件用于渲染 <Document>
中加载的 PDF 的特定某一页。
pageNumber
属性 (必需): 你想显示的页码(从 1 开始)。width
/height
属性: 控制渲染出页面的宽度或高度(像素)。两者设置一个即可,页面会按比例缩放。scale
属性 : 按比例缩放页面,scale={1.5}
表示放大 50%。renderTextLayer
属性 :布尔值,是否渲染文本层。设为true
(默认) 可以让用户选择和复制文本。renderAnnotationLayer
属性 : 布尔值,是否渲染注释层。设为true
(默认) 可以显示 PDF 中的链接等注释。
4. 常见功能配置
掌握了基础组件后,我们来实现一些实用的功能。
实现分页控制(上一页/下一页)
基于我们快速上手的例子,添加两个按钮来控制页面切换。
jsx
// src/PdfViewer.js (完整版)
import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
function PdfViewer() {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
function onDocumentLoadSuccess({ numPages }) {
setNumPages(numPages);
setPageNumber(1); // 加载新文档时,重置到第一页
}
function goToPrevPage() {
setPageNumber(prevPageNumber => Math.max(prevPageNumber - 1, 1));
}
function goToNextPage() {
setPageNumber(prevPageNumber => Math.min(prevPageNumber + 1, numPages));
}
return (
<div>
<nav>
<button onClick={goToPrevPage} disabled={pageNumber <= 1}>
Prev
</button>
<button onClick={goToNextPage} disabled={pageNumber >= numPages}>
Next
</button>
</nav>
<div style={{ border: '1px solid black', marginTop: '10px' }}>
<Document
file="/sample.pdf"
onLoadSuccess={onDocumentLoadSuccess}
>
<Page pageNumber={pageNumber} />
</Document>
</div>
<p>
Page {pageNumber} of {numPages}
</p>
</div>
);
}
export default PdfViewer;
控制 PDF 尺寸与缩放
你可以通过 <Page>
的 width
或 scale
属性来控制大小。
方法一:固定宽度
jsx
<Page pageNumber={pageNumber} width={600} />
方法二:动态缩放
jsx
const [scale, setScale] = useState(1.0);
// ...
<div>
<button onClick={() => setScale(s => s - 0.1)}>-</button>
<span>Zoom: {Math.round(scale * 100)}%</span>
<button onClick={() => setScale(s => s + 0.1)}>+</button>
</div>
<Page pageNumber={pageNumber} scale={scale} />
加载不同来源的 PDF
file
属性非常灵活:
-
从 URL 加载 :
jsx<Document file="https://example.com/document.pdf" />
-
从用户上传的文件加载 :
jsxconst [file, setFile] = useState(null); function onFileChange(event) { setFile(event.target.files[0]); } return ( <div> <input type="file" onChange={onFileChange} /> {file && <Document file={file}>...</Document>} </div> );
-
从 Base64 字符串加载 :
jsxconst base64string = 'data:application/pdf;base64,JVBERi0xLj...'; <Document file={base64string} />
开启文本选择与注释层
默认情况下,renderTextLayer
和 renderAnnotationLayer
都是 true
。如果你发现无法选择文本或点击链接,请确保:
- 你没有将它们设置为
false
。 - 你已经正确引入了 CSS 文件,如快速上手部分所示。这是最常见的原因。
jsx
import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
// ...
<Page
pageNumber={pageNumber}
renderTextLayer={true}
renderAnnotationLayer={true}
/>
5. 常见问题与最佳实践
错误:"Failed to load PDF worker"
这是最常见的问题。这意味着 react-pdf
找不到 pdf.worker.min.js
文件。
解决方案:
-
确认你已在应用入口配置了 workerSrc,如本文第二部分所示。
-
对于 Create React App (v4 及以下) 或其他基于 Webpack 的项目,你可能需要手动复制 worker 文件到
public
目录。- 安装
copy-webpack-plugin
:npm install --save-dev copy-webpack-plugin
- 在
config-overrides.js
(如果你用了react-app-rewired
) 或webpack.config.js
中配置:
javascriptconst CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { //... plugins: [ new CopyWebpackPlugin({ patterns: [ { from: 'node_modules/pdfjs-dist/build/pdf.worker.min.js', to: '' } ] }) ] };
- 然后将
workerSrc
设置为/pdf.worker.min.js
。
- 安装
性能优化
-
Memoization : 如果你的
PdfViewer
组件因为父组件重渲染而频繁刷新,使用React.memo
包裹它可以避免不必要的重渲染。 -
CDN 加载 Worker : 为了减小打包体积,你可以从 CDN 加载 worker:
jsxpdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
6. 总结
react-pdf
是一个强大而直观的工具,它极大地简化了在 React 应用中展示 PDF 的复杂性。通过 <Document>
和 <Page>
这两个核心组件,结合简单的状态管理,你就可以快速实现功能完备的 PDF 阅读器,包括分页、缩放和文本交互。
记住配置 PDF.js worker
的关键步骤,并善用 onLoadSuccess
回调来构建你的交互逻辑,你将能够为你的用户提供流畅的 PDF 预览体验。