React-PDF 完全指南:在你的应用中优雅地展示 PDF

在 Web 应用中,直接预览和展示 PDF 文件是一项非常普遍的需求,例如在线查看发票、合同、电子书或用户上传的文档。react-pdf 就是解决这一问题的优秀库,它能让你在 React 应用中轻松地渲染和控制 PDF 文档的显示。

本文将作为一份详细的入门指南,带你从零开始,快速掌握 react-pdf 的核心用法,并配置常见功能,如分页、缩放等。

目录

  1. [什么是 react-pdf?](#什么是 react-pdf? "#1-%E4%BB%80%E4%B9%88%E6%98%AF-react-pdf")
  2. [快速上手:展示你的第一个 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")
  3. 核心组件详解
  4. 常见功能配置
  5. 常见问题与最佳实践
    • [错误:"Failed to load PDF worker"](#错误:"Failed to load PDF worker" "#%E9%94%99%E8%AF%AFfailed-to-load-pdf-worker")
    • 性能优化
  6. 总结

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-appVite 创建)。

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.jssrc/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 的第一页开始。

  1. 准备一个 PDF 文件。你可以从网上下载一个示例 PDF,或者使用自己的文件。为了简单起见,将它放在 public 文件夹下,例如 public/sample.pdf

  2. 创建 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 文件。

  1. 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>widthscale 属性来控制大小。

方法一:固定宽度

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" />
  • 从用户上传的文件加载 :

    jsx 复制代码
    const [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 字符串加载 :

    jsx 复制代码
    const base64string = 'data:application/pdf;base64,JVBERi0xLj...';
    <Document file={base64string} />

开启文本选择与注释层

默认情况下,renderTextLayerrenderAnnotationLayer 都是 true。如果你发现无法选择文本或点击链接,请确保:

  1. 你没有将它们设置为 false
  2. 你已经正确引入了 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 文件。

解决方案:

  1. 确认你已在应用入口配置了 workerSrc,如本文第二部分所示。

  2. 对于 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 中配置:
    javascript 复制代码
    const 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:

    jsx 复制代码
    pdfjs.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 预览体验。

相关推荐
lichenyang4534 小时前
React ajax中的跨域以及代理服务器
前端·react.js·ajax
好了来看下一题6 小时前
使用 React+Vite+Electron 搭建桌面应用
前端·react.js·electron
啃火龙果的兔子6 小时前
前端八股文-react篇
前端·react.js·前端框架
刺客-Andy6 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
萌萌哒草头将军8 小时前
🚀🚀🚀VSCode 发布 1.101 版本,Copilot 更全能!
前端·vue.js·react.js
Jimmy9 小时前
CSS 中操作移动,缩放和旋转
前端·css·react.js
白瓷梅子汤12 小时前
跟着官方示例学习 @tanStack-table --- Row Pinning
前端·react.js
三天不学习14 小时前
一键实现全站多语言化:translate.js 极简集成指南,支持Vue 、React 框架。
javascript·vue.js·react.js·多语言·translate.js