902 lines
22 KiB
Markdown
902 lines
22 KiB
Markdown
# React-PDF 功能总结文档
|
||
|
||
> **库版本**: react-pdf v9.2.1
|
||
> **基于**: Mozilla PDF.js
|
||
> **测试Demo**: `/pdf-demo`
|
||
> **创建时间**: 2025-11-22
|
||
|
||
---
|
||
|
||
## 📋 目录
|
||
|
||
- [1. 库概述](#1-库概述)
|
||
- [2. 核心功能](#2-核心功能)
|
||
- [3. 内置功能详解](#3-内置功能详解)
|
||
- [4. API参考](#4-api参考)
|
||
- [5. 实践示例](#5-实践示例)
|
||
- [6. 性能优化](#6-性能优化)
|
||
- [7. 常见问题](#7-常见问题)
|
||
|
||
---
|
||
|
||
## 1. 库概述
|
||
|
||
### 1.1 什么是 react-pdf?
|
||
|
||
`react-pdf` 是一个用于在 React 应用中**显示和预览 PDF 文件**的库,基于 Mozilla 的 PDF.js 引擎构建。
|
||
|
||
### 1.2 主要特性
|
||
|
||
- ✅ **查看 PDF** - 在浏览器中渲染和显示 PDF 文档
|
||
- ✅ **多页支持** - 支持渲染单页或所有页面
|
||
- ✅ **文本选择** - 启用文本层后可选择和复制文本
|
||
- ✅ **缩放控制** - 内置缩放和旋转功能
|
||
- ✅ **多种渲染模式** - Canvas 或 SVG 渲染
|
||
- ✅ **注释支持** - 显示 PDF 内置注释
|
||
- ✅ **响应式** - 适应不同屏幕尺寸
|
||
- ✅ **高性能** - 基于 WebWorker 的异步渲染
|
||
|
||
### 1.3 与 @react-pdf/renderer 的区别
|
||
|
||
| 特性 | react-pdf | @react-pdf/renderer |
|
||
|------|-----------|---------------------|
|
||
| **用途** | 查看已有PDF | 生成新PDF |
|
||
| **输入** | PDF文件 | React组件 |
|
||
| **输出** | 浏览器渲染 | PDF文件 |
|
||
| **文本选择** | ✅ 支持 | ❌ 不适用 |
|
||
| **交互性** | ✅ 高 | ❌ 无 |
|
||
|
||
---
|
||
|
||
## 2. 核心功能
|
||
|
||
### 2.1 基础文档渲染
|
||
|
||
#### 2.1.1 Document 组件
|
||
|
||
**⚠️ 第一步:导入必需的 CSS 文件**
|
||
|
||
```typescript
|
||
import { Document, Page, pdfjs } from 'react-pdf';
|
||
|
||
// 🔑 必须导入CSS,否则文本层无法工作!
|
||
import 'react-pdf/dist/Page/TextLayer.css';
|
||
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||
|
||
// 配置Worker
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||
```
|
||
|
||
**基本使用:**
|
||
|
||
```typescript
|
||
<Document
|
||
file={pdfUrl} // PDF文件URL或ArrayBuffer
|
||
onLoadSuccess={onDocumentLoadSuccess} // 加载成功回调
|
||
onLoadError={onDocumentLoadError} // 加载失败回调
|
||
onLoadProgress={onLoadProgress} // 加载进度回调
|
||
loading={<div>加载中...</div>} // 自定义加载组件
|
||
error={<div>加载失败</div>} // 自定义错误组件
|
||
noData={<div>无数据</div>} // 无数据组件
|
||
>
|
||
{/* Page 组件 */}
|
||
</Document>
|
||
```
|
||
|
||
**支持的文件格式**:
|
||
- URL 字符串: `"http://example.com/file.pdf"`
|
||
- File 对象: `new File([blob], "file.pdf")`
|
||
- ArrayBuffer: 二进制数据
|
||
- Base64: `"data:application/pdf;base64,JVBERi0x..."`
|
||
|
||
#### 2.1.2 Page 组件
|
||
|
||
```typescript
|
||
import { Page } from 'react-pdf';
|
||
|
||
<Page
|
||
pageNumber={1} // 页码(从1开始)
|
||
scale={1.0} // 缩放比例
|
||
rotate={0} // 旋转角度(0, 90, 180, 270)
|
||
renderTextLayer={true} // 启用文本层
|
||
renderAnnotationLayer={true} // 启用注释层
|
||
renderMode="canvas" // 渲染模式: "canvas" | "svg"
|
||
devicePixelRatio={window.devicePixelRatio} // 设备像素比
|
||
onLoadSuccess={onPageLoadSuccess} // 页面加载成功回调
|
||
onLoadError={onPageLoadError} // 页面加载失败回调
|
||
className="pdf-page" // 自定义样式类
|
||
/>
|
||
```
|
||
|
||
### 2.2 事件回调
|
||
|
||
#### 2.2.1 Document 事件
|
||
|
||
```typescript
|
||
// 加载成功
|
||
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
|
||
console.log('PDF总页数:', numPages);
|
||
}
|
||
|
||
// 加载失败
|
||
function onDocumentLoadError(error: Error) {
|
||
console.error('PDF加载失败:', error.message);
|
||
}
|
||
|
||
// 加载进度
|
||
function onDocumentLoadProgress({ loaded, total }: { loaded: number; total: number }) {
|
||
const progress = Math.round((loaded / total) * 100);
|
||
console.log('加载进度:', progress + '%');
|
||
}
|
||
```
|
||
|
||
#### 2.2.2 Page 事件
|
||
|
||
```typescript
|
||
// 页面加载成功
|
||
function onPageLoadSuccess(page: any) {
|
||
console.log('页面渲染成功:', page.pageNumber);
|
||
console.log('页面尺寸:', page.width, page.height);
|
||
}
|
||
|
||
// 页面加载失败
|
||
function onPageLoadError(error: Error) {
|
||
console.error('页面渲染失败:', error);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 内置功能详解
|
||
|
||
### 3.1 缩放功能 (Scale)
|
||
|
||
#### 3.1.1 基本使用
|
||
|
||
```typescript
|
||
const [scale, setScale] = useState(1.0);
|
||
|
||
// 放大
|
||
const handleZoomIn = () => {
|
||
setScale(prev => Math.min(prev + 0.25, 3.0)); // 最大300%
|
||
};
|
||
|
||
// 缩小
|
||
const handleZoomOut = () => {
|
||
setScale(prev => Math.max(prev - 0.25, 0.5)); // 最小50%
|
||
};
|
||
|
||
// 应用缩放
|
||
<Page pageNumber={1} scale={scale} />
|
||
```
|
||
|
||
#### 3.1.2 缩放建议值
|
||
|
||
| 缩放级别 | scale值 | 使用场景 |
|
||
|---------|---------|----------|
|
||
| 极小 | 0.5 | 预览缩略图 |
|
||
| 小 | 0.75 | 查看概览 |
|
||
| 正常 | 1.0 | 默认阅读 |
|
||
| 大 | 1.5 | 放大查看细节 |
|
||
| 极大 | 2.0-3.0 | 精细查看文本 |
|
||
|
||
#### 3.1.3 注意事项
|
||
|
||
- ⚠️ 缩放会影响性能,建议范围 0.5-3.0
|
||
- ⚠️ 缩放不会改变文本层坐标,需要手动调整高亮位置
|
||
- ✅ 使用 `transform: scale()` CSS 可以实现视觉缩放但不重新渲染
|
||
|
||
### 3.2 旋转功能 (Rotate)
|
||
|
||
#### 3.2.1 基本使用
|
||
|
||
```typescript
|
||
const [rotation, setRotation] = useState(0);
|
||
|
||
// 左转90度
|
||
const handleRotateLeft = () => {
|
||
setRotation(prev => (prev - 90) % 360);
|
||
};
|
||
|
||
// 右转90度
|
||
const handleRotateRight = () => {
|
||
setRotation(prev => (prev + 90) % 360);
|
||
};
|
||
|
||
// 应用旋转
|
||
<Page pageNumber={1} rotate={rotation} />
|
||
```
|
||
|
||
#### 3.2.2 支持的旋转角度
|
||
|
||
- `0°` - 正常方向
|
||
- `90°` - 顺时针旋转90度
|
||
- `180°` - 倒置
|
||
- `270°` - 顺时针旋转270度(或逆时针90度)
|
||
|
||
**注意**: 仅支持 90 度的倍数,其他角度无效。
|
||
|
||
### 3.3 文本层 (Text Layer)
|
||
|
||
#### 3.3.1 启用文本层
|
||
|
||
**⚠️ 重要:必须导入 CSS 文件!**
|
||
|
||
```typescript
|
||
// 导入react-pdf的CSS样式(文本层必需)
|
||
import 'react-pdf/dist/Page/TextLayer.css';
|
||
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||
|
||
<Page
|
||
pageNumber={1}
|
||
renderTextLayer={true} // ✅ 启用文本层
|
||
/>
|
||
```
|
||
|
||
**常见错误**:如果不导入CSS,文本层虽然会渲染,但文本**无法被选择**!
|
||
|
||
#### 3.3.2 文本层功能
|
||
|
||
启用后可实现:
|
||
|
||
1. **文本选择** - 用户可以用鼠标选中文本
|
||
2. **文本复制** - 选中后可以复制文本
|
||
3. **文本搜索** - 浏览器原生搜索功能 (Ctrl+F)
|
||
4. **屏幕阅读器** - 可访问性支持
|
||
|
||
#### 3.3.3 文本选择监听
|
||
|
||
```typescript
|
||
const handleTextSelection = () => {
|
||
const selection = window.getSelection();
|
||
if (!selection || selection.isCollapsed) return;
|
||
|
||
const selectedText = selection.toString();
|
||
console.log('选中文本:', selectedText);
|
||
};
|
||
|
||
<div onMouseUp={handleTextSelection}>
|
||
<Page pageNumber={1} renderTextLayer={true} />
|
||
</div>
|
||
```
|
||
|
||
#### 3.3.4 文本层样式
|
||
|
||
默认文本层是**透明的**,覆盖在 Canvas 渲染的 PDF 上方。可以通过 CSS 自定义:
|
||
|
||
```css
|
||
/* 文本层容器 */
|
||
.react-pdf__Page__textContent {
|
||
/* 默认透明,文本不可见但可选择 */
|
||
opacity: 0.2; /* 调试时可以显示文本层 */
|
||
}
|
||
|
||
/* 文本层中的文本 */
|
||
.react-pdf__Page__textContent span {
|
||
color: transparent; /* 文本透明 */
|
||
position: absolute;
|
||
white-space: pre;
|
||
}
|
||
```
|
||
|
||
### 3.4 注释层 (Annotation Layer)
|
||
|
||
#### 3.4.1 启用注释层
|
||
|
||
```typescript
|
||
<Page
|
||
pageNumber={1}
|
||
renderAnnotationLayer={true} // ✅ 启用注释层
|
||
/>
|
||
```
|
||
|
||
#### 3.4.2 支持的注释类型
|
||
|
||
- 📝 文本注释 (Text Annotations)
|
||
- 🔗 链接注释 (Link Annotations)
|
||
- 📎 附件注释 (File Attachment Annotations)
|
||
- 🎨 高亮/下划线 (Highlight/Underline)
|
||
- 💬 弹出注释 (Popup Annotations)
|
||
|
||
#### 3.4.3 注释交互
|
||
|
||
- ✅ 链接可点击(内部链接、外部URL)
|
||
- ✅ 注释可以悬停显示提示
|
||
- ✅ 表单字段可交互(如果PDF包含表单)
|
||
|
||
### 3.5 渲染模式 (Render Mode)
|
||
|
||
#### 3.5.1 Canvas 模式(默认)
|
||
|
||
```typescript
|
||
<Page pageNumber={1} renderMode="canvas" />
|
||
```
|
||
|
||
**特点**:
|
||
- ✅ 性能最佳
|
||
- ✅ 内存占用低
|
||
- ✅ 适合大多数场景
|
||
- ❌ 缩放时可能模糊
|
||
- ❌ 打印质量一般
|
||
|
||
#### 3.5.2 SVG 模式
|
||
|
||
```typescript
|
||
<Page pageNumber={1} renderMode="svg" />
|
||
```
|
||
|
||
**特点**:
|
||
- ✅ 矢量渲染,无限缩放不失真
|
||
- ✅ 打印质量极佳
|
||
- ✅ 支持复杂图形
|
||
- ❌ 性能较低
|
||
- ❌ 内存占用高
|
||
- ❌ 不适合大文档
|
||
|
||
#### 3.5.3 选择建议
|
||
|
||
| 场景 | 推荐模式 | 原因 |
|
||
|------|---------|------|
|
||
| 普通阅读 | Canvas | 性能好 |
|
||
| 高清打印 | SVG | 质量高 |
|
||
| 移动端 | Canvas | 省内存 |
|
||
| 频繁缩放 | SVG | 不失真 |
|
||
| 大文档(>50页) | Canvas | 性能 |
|
||
|
||
### 3.6 设备像素比 (Device Pixel Ratio)
|
||
|
||
#### 3.6.1 基本使用
|
||
|
||
```typescript
|
||
<Page
|
||
pageNumber={1}
|
||
devicePixelRatio={window.devicePixelRatio || 1}
|
||
/>
|
||
```
|
||
|
||
#### 3.6.2 作用
|
||
|
||
- 🖥️ 在高分辨率屏幕(Retina屏)上提高清晰度
|
||
- 📱 适配不同设备的像素密度
|
||
- ⚡ 值越大,渲染质量越高,但性能消耗也越大
|
||
|
||
**常见值**:
|
||
- 普通屏幕: `1`
|
||
- Retina屏: `2`
|
||
- 4K屏: `3` 或更高
|
||
|
||
**建议**: 使用 `window.devicePixelRatio` 自动适配。
|
||
|
||
### 3.7 Worker 配置
|
||
|
||
#### 3.7.1 设置 Worker 路径
|
||
|
||
```typescript
|
||
import { pdfjs } from 'react-pdf';
|
||
|
||
// 使用本地worker文件
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||
|
||
// 或使用CDN
|
||
pdfjs.GlobalWorkerOptions.workerSrc =
|
||
`//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
|
||
```
|
||
|
||
#### 3.7.2 Worker 作用
|
||
|
||
- ⚡ **异步渲染** - 在后台线程处理PDF解析
|
||
- 🚫 **不阻塞UI** - 主线程不会被卡住
|
||
- 📈 **性能提升** - 大文件加载更流畅
|
||
|
||
---
|
||
|
||
## 4. API 参考
|
||
|
||
### 4.1 Document Props
|
||
|
||
| 属性 | 类型 | 默认值 | 说明 |
|
||
|-----|------|--------|------|
|
||
| `file` | `string \| File \| ArrayBuffer \| object` | **必填** | PDF文件源 |
|
||
| `loading` | `ReactNode` | `"Loading PDF..."` | 加载中显示的内容 |
|
||
| `error` | `ReactNode` | `"Failed to load PDF"` | 加载失败显示的内容 |
|
||
| `noData` | `ReactNode` | `"No PDF file specified"` | 无数据时显示的内容 |
|
||
| `onLoadSuccess` | `(pdf) => void` | - | 加载成功回调 |
|
||
| `onLoadError` | `(error) => void` | - | 加载失败回调 |
|
||
| `onLoadProgress` | `(progress) => void` | - | 加载进度回调 |
|
||
| `onSourceSuccess` | `() => void` | - | 文件源加载成功回调 |
|
||
| `onSourceError` | `(error) => void` | - | 文件源加载失败回调 |
|
||
| `rotate` | `0 \| 90 \| 180 \| 270` | `0` | 旋转角度 |
|
||
| `className` | `string` | - | CSS类名 |
|
||
| `inputRef` | `RefObject` | - | 引用 |
|
||
|
||
### 4.2 Page Props
|
||
|
||
| 属性 | 类型 | 默认值 | 说明 |
|
||
|-----|------|--------|------|
|
||
| `pageNumber` | `number` | **必填** | 页码(从1开始) |
|
||
| `scale` | `number` | `1.0` | 缩放比例 |
|
||
| `rotate` | `0 \| 90 \| 180 \| 270` | `0` | 旋转角度 |
|
||
| `width` | `number` | - | 页面宽度(px) |
|
||
| `height` | `number` | - | 页面高度(px) |
|
||
| `renderTextLayer` | `boolean` | `true` | 是否渲染文本层 |
|
||
| `renderAnnotationLayer` | `boolean` | `true` | 是否渲染注释层 |
|
||
| `renderMode` | `"canvas" \| "svg"` | `"canvas"` | 渲染模式 |
|
||
| `devicePixelRatio` | `number` | `1` | 设备像素比 |
|
||
| `onLoadSuccess` | `(page) => void` | - | 页面加载成功回调 |
|
||
| `onLoadError` | `(error) => void` | - | 页面加载失败回调 |
|
||
| `onRenderSuccess` | `() => void` | - | 渲染成功回调 |
|
||
| `onRenderError` | `(error) => void` | - | 渲染失败回调 |
|
||
| `className` | `string` | - | CSS类名 |
|
||
| `inputRef` | `RefObject` | - | 引用 |
|
||
|
||
### 4.3 回调函数参数
|
||
|
||
#### onDocumentLoadSuccess
|
||
|
||
```typescript
|
||
interface PDFDocumentProxy {
|
||
numPages: number; // 总页数
|
||
fingerprints: string[]; // 文档指纹
|
||
getPage(pageNumber: number): Promise<PDFPageProxy>;
|
||
getMetadata(): Promise<Metadata>;
|
||
// ... 更多方法
|
||
}
|
||
```
|
||
|
||
#### onPageLoadSuccess
|
||
|
||
```typescript
|
||
interface PDFPageProxy {
|
||
pageNumber: number; // 页码
|
||
rotate: number; // 旋转角度
|
||
ref: object; // 页面引用
|
||
userUnit: number; // 用户单位
|
||
view: number[]; // 视图矩形 [x1, y1, x2, y2]
|
||
width: number; // 原始宽度
|
||
height: number; // 原始高度
|
||
// ... 更多属性和方法
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 实践示例
|
||
|
||
### 5.1 完整的PDF查看器
|
||
|
||
```typescript
|
||
import { useState } from 'react';
|
||
import { Document, Page, pdfjs } from 'react-pdf';
|
||
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||
|
||
function PdfViewer({ url }: { url: string }) {
|
||
const [numPages, setNumPages] = useState<number | null>(null);
|
||
const [pageNumber, setPageNumber] = useState(1);
|
||
const [scale, setScale] = useState(1.0);
|
||
|
||
return (
|
||
<div>
|
||
{/* 工具栏 */}
|
||
<div className="toolbar">
|
||
<button onClick={() => setScale(s => s + 0.25)}>放大</button>
|
||
<button onClick={() => setScale(s => s - 0.25)}>缩小</button>
|
||
<span>缩放: {Math.round(scale * 100)}%</span>
|
||
|
||
<button
|
||
onClick={() => setPageNumber(p => Math.max(1, p - 1))}
|
||
disabled={pageNumber <= 1}
|
||
>
|
||
上一页
|
||
</button>
|
||
<span>页码: {pageNumber} / {numPages}</span>
|
||
<button
|
||
onClick={() => setPageNumber(p => Math.min(numPages || 1, p + 1))}
|
||
disabled={!numPages || pageNumber >= numPages}
|
||
>
|
||
下一页
|
||
</button>
|
||
</div>
|
||
|
||
{/* PDF渲染 */}
|
||
<Document
|
||
file={url}
|
||
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
|
||
>
|
||
<Page
|
||
pageNumber={pageNumber}
|
||
scale={scale}
|
||
renderTextLayer={true}
|
||
/>
|
||
</Document>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 5.2 渲染所有页面
|
||
|
||
```typescript
|
||
function PdfAllPages({ url }: { url: string }) {
|
||
const [numPages, setNumPages] = useState<number | null>(null);
|
||
|
||
return (
|
||
<Document
|
||
file={url}
|
||
onLoadSuccess={({ numPages }) => setNumPages(numPages)}
|
||
>
|
||
{numPages && Array.from({ length: numPages }, (_, i) => (
|
||
<div key={i + 1} className="page-container">
|
||
<p>第 {i + 1} 页</p>
|
||
<Page pageNumber={i + 1} />
|
||
</div>
|
||
))}
|
||
</Document>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 5.3 自定义高亮功能
|
||
|
||
```typescript
|
||
function PdfWithHighlight({ url }: { url: string }) {
|
||
const [highlights, setHighlights] = useState<any[]>([]);
|
||
|
||
const handleTextSelection = () => {
|
||
const selection = window.getSelection();
|
||
if (!selection || selection.isCollapsed) return;
|
||
|
||
const range = selection.getRangeAt(0);
|
||
const rects = Array.from(range.getClientRects());
|
||
|
||
// 计算高亮区域相对位置
|
||
const pageElement = range.startContainer.parentElement
|
||
?.closest('[data-page-number]');
|
||
|
||
if (!pageElement) return;
|
||
|
||
const pageRect = pageElement.getBoundingClientRect();
|
||
const pageNumber = parseInt(
|
||
pageElement.getAttribute('data-page-number') || '1'
|
||
);
|
||
|
||
const newHighlight = {
|
||
pageNumber,
|
||
rects: rects.map(rect => ({
|
||
left: rect.left - pageRect.left,
|
||
top: rect.top - pageRect.top,
|
||
width: rect.width,
|
||
height: rect.height
|
||
})),
|
||
text: selection.toString()
|
||
};
|
||
|
||
setHighlights(prev => [...prev, newHighlight]);
|
||
};
|
||
|
||
return (
|
||
<div onMouseUp={handleTextSelection}>
|
||
<Document file={url}>
|
||
<div style={{ position: 'relative' }}>
|
||
<Page pageNumber={1} renderTextLayer={true} />
|
||
|
||
{/* 渲染高亮层 */}
|
||
{highlights.map((hl, idx) => (
|
||
<div key={idx}>
|
||
{hl.rects.map((rect: any, i: number) => (
|
||
<div
|
||
key={i}
|
||
style={{
|
||
position: 'absolute',
|
||
left: rect.left,
|
||
top: rect.top,
|
||
width: rect.width,
|
||
height: rect.height,
|
||
backgroundColor: 'yellow',
|
||
opacity: 0.3,
|
||
pointerEvents: 'none'
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Document>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 5.4 响应式PDF查看器
|
||
|
||
```typescript
|
||
import { useRef, useEffect, useState } from 'react';
|
||
|
||
function ResponsivePdfViewer({ url }: { url: string }) {
|
||
const containerRef = useRef<HTMLDivElement>(null);
|
||
const [width, setWidth] = useState<number>(0);
|
||
|
||
useEffect(() => {
|
||
const updateWidth = () => {
|
||
if (containerRef.current) {
|
||
setWidth(containerRef.current.offsetWidth);
|
||
}
|
||
};
|
||
|
||
updateWidth();
|
||
window.addEventListener('resize', updateWidth);
|
||
return () => window.removeEventListener('resize', updateWidth);
|
||
}, []);
|
||
|
||
return (
|
||
<div ref={containerRef}>
|
||
<Document file={url}>
|
||
<Page pageNumber={1} width={width} />
|
||
</Document>
|
||
</div>
|
||
);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 性能优化
|
||
|
||
### 6.1 延迟加载 (Lazy Loading)
|
||
|
||
```typescript
|
||
import { lazy, Suspense } from 'react';
|
||
|
||
const Document = lazy(() => import('react-pdf').then(m => ({ default: m.Document })));
|
||
const Page = lazy(() => import('react-pdf').then(m => ({ default: m.Page })));
|
||
|
||
function LazyPdfViewer() {
|
||
return (
|
||
<Suspense fallback={<div>加载PDF组件...</div>}>
|
||
<Document file="document.pdf">
|
||
<Page pageNumber={1} />
|
||
</Document>
|
||
</Suspense>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 6.2 虚拟滚动
|
||
|
||
对于大文档(>100页),使用虚拟滚动只渲染可见页面:
|
||
|
||
```typescript
|
||
import { FixedSizeList } from 'react-window';
|
||
|
||
function VirtualPdfViewer({ url, numPages }: any) {
|
||
const Row = ({ index, style }: any) => (
|
||
<div style={style}>
|
||
<Page pageNumber={index + 1} width={800} />
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<FixedSizeList
|
||
height={800}
|
||
itemCount={numPages}
|
||
itemSize={1100}
|
||
width="100%"
|
||
>
|
||
{Row}
|
||
</FixedSizeList>
|
||
);
|
||
}
|
||
```
|
||
|
||
### 6.3 缓存策略
|
||
|
||
```typescript
|
||
// 使用缓存避免重复加载
|
||
const pdfCache = new Map();
|
||
|
||
async function loadPdfWithCache(url: string) {
|
||
if (pdfCache.has(url)) {
|
||
return pdfCache.get(url);
|
||
}
|
||
|
||
const response = await fetch(url);
|
||
const blob = await response.blob();
|
||
pdfCache.set(url, blob);
|
||
|
||
return blob;
|
||
}
|
||
```
|
||
|
||
### 6.4 优化建议
|
||
|
||
| 优化项 | 建议 | 影响 |
|
||
|-------|------|------|
|
||
| **缩放范围** | 限制在 0.5-3.0 | 性能 +++
|
||
| **按需渲染** | 只渲染可见页面 | 性能 +++++ |
|
||
| **Worker** | 必须启用 | 性能 +++++ |
|
||
| **文本层** | 按需启用 | 性能 +++ |
|
||
| **SVG模式** | 仅在需要时使用 | 性能 ++ |
|
||
| **devicePixelRatio** | 限制最大值为2 | 性能 +++ |
|
||
|
||
---
|
||
|
||
## 7. 常见问题
|
||
|
||
### 7.1 Worker 相关
|
||
|
||
**Q: 为什么出现 "Cannot read property 'getDocument' of undefined" 错误?**
|
||
|
||
A: 没有正确配置 Worker 路径。
|
||
|
||
```typescript
|
||
// ❌ 错误
|
||
import { Document, Page } from 'react-pdf';
|
||
|
||
// ✅ 正确
|
||
import { Document, Page, pdfjs } from 'react-pdf';
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
|
||
```
|
||
|
||
**Q: Worker 文件从哪里获取?**
|
||
|
||
A:
|
||
1. 从 `node_modules/pdfjs-dist/build/pdf.worker.js` 复制到 `public/` 目录
|
||
2. 或使用 CDN: `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`
|
||
|
||
### 7.2 渲染问题
|
||
|
||
**Q: PDF 显示模糊怎么办?**
|
||
|
||
A:
|
||
1. 提高 `scale` 值
|
||
2. 设置 `devicePixelRatio={window.devicePixelRatio}`
|
||
3. 使用 SVG 渲染模式
|
||
|
||
**Q: PDF 加载很慢怎么办?**
|
||
|
||
A:
|
||
1. 压缩 PDF 文件
|
||
2. 使用虚拟滚动
|
||
3. 按需加载页面
|
||
4. 启用 Worker(默认启用)
|
||
|
||
### 7.3 文本选择问题
|
||
|
||
**Q: 文本层启用了但无法选择文本?**
|
||
|
||
A: **最常见原因是没有导入 CSS 文件!**
|
||
|
||
```typescript
|
||
// ✅ 正确:导入CSS
|
||
import 'react-pdf/dist/Page/TextLayer.css';
|
||
import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||
|
||
<Page pageNumber={1} renderTextLayer={true} />
|
||
```
|
||
|
||
其他可能的原因:
|
||
1. 确认 `renderTextLayer={true}`
|
||
2. 检查 CSS 是否覆盖了文本层 (`pointer-events: none`)
|
||
3. 检查文本层是否被其他元素遮挡(`z-index`)
|
||
4. 确保容器允许文本选择(`user-select: text`)
|
||
|
||
**Q: 文本层位置不准确?**
|
||
|
||
A: 文本层坐标受缩放影响,需要在高亮时调整:
|
||
|
||
```typescript
|
||
const adjustedRect = {
|
||
left: rect.left / scale,
|
||
top: rect.top / scale,
|
||
width: rect.width / scale,
|
||
height: rect.height / scale
|
||
};
|
||
```
|
||
|
||
### 7.4 样式问题
|
||
|
||
**Q: 如何自定义 PDF 页面样式?**
|
||
|
||
A:
|
||
|
||
```css
|
||
/* 自定义页面边框 */
|
||
.react-pdf__Page {
|
||
border: 1px solid #ccc;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
/* 自定义Canvas样式 */
|
||
.react-pdf__Page__canvas {
|
||
max-width: 100%;
|
||
height: auto !important;
|
||
}
|
||
|
||
/* 自定义文本层样式 */
|
||
.react-pdf__Page__textContent {
|
||
/* 调试时显示文本层 */
|
||
opacity: 0.2;
|
||
}
|
||
```
|
||
|
||
### 7.5 CORS 问题
|
||
|
||
**Q: 加载外部 PDF 出现 CORS 错误?**
|
||
|
||
A:
|
||
1. 使用代理服务器
|
||
2. 配置服务器 CORS 头
|
||
3. 使用 `fetch` 先下载再传递 Blob
|
||
|
||
```typescript
|
||
async function loadPdfWithCors(url: string) {
|
||
const response = await fetch(url, { mode: 'cors' });
|
||
const blob = await response.blob();
|
||
return blob;
|
||
}
|
||
|
||
<Document file={blob} />
|
||
```
|
||
|
||
### 7.6 TypeScript 类型问题
|
||
|
||
**Q: TypeScript 类型定义缺失?**
|
||
|
||
A:
|
||
|
||
```bash
|
||
npm install --save-dev @types/react-pdf
|
||
```
|
||
|
||
或手动声明:
|
||
|
||
```typescript
|
||
declare module 'react-pdf' {
|
||
export const Document: any;
|
||
export const Page: any;
|
||
export const pdfjs: any;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 总结
|
||
|
||
### 8.1 核心优势
|
||
|
||
1. ✅ **功能完善** - 支持缩放、旋转、文本选择等核心功能
|
||
2. ✅ **性能优异** - Worker 异步渲染,不阻塞UI
|
||
3. ✅ **灵活配置** - Canvas/SVG 双渲染模式
|
||
4. ✅ **易于集成** - React组件化,API简洁
|
||
5. ✅ **活跃维护** - 基于 PDF.js,持续更新
|
||
|
||
### 8.2 适用场景
|
||
|
||
- ✅ 文档预览系统
|
||
- ✅ 在线阅读器
|
||
- ✅ 文档审核系统
|
||
- ✅ 电子签名应用
|
||
- ✅ 在线表单填写
|
||
|
||
### 8.3 不适用场景
|
||
|
||
- ❌ 生成PDF(使用 @react-pdf/renderer)
|
||
- ❌ 编辑PDF内容(使用 PDF.js API或其他库)
|
||
- ❌ PDF合并/拆分(使用 pdf-lib)
|
||
|
||
---
|
||
|
||
## 9. 参考资源
|
||
|
||
- 📖 [官方文档](https://github.com/wojtekmaj/react-pdf)
|
||
- 📖 [PDF.js 文档](https://mozilla.github.io/pdf.js/)
|
||
- 📦 [NPM Package](https://www.npmjs.com/package/react-pdf)
|
||
- 🎮 [在线Demo](http://projects.wojtekmaj.pl/react-pdf/)
|
||
- 💬 [GitHub Issues](https://github.com/wojtekmaj/react-pdf/issues)
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**最后更新**: 2025-11-22
|
||
**维护者**: Claude Code 🤖
|