在 React 中实现一个 PDF Viewer,看似简单,但在真实工程中通常涉及一个关键事实:
React PDF Viewer 的工程实现通常分为三种主流路径:iframe 嵌入 PDF.js Viewer、自建 PDF.js 渲染层,以及基于 PDF.js Viewer 的 React SDK 封装方案。
大多数开发者并不是直接使用 PDF.js 的底层 API 来渲染每一页 canvas,而是基于 PDF.js Viewer(官方
viewer.html或其定制版本)进行集成。
这一区别至关重要——它决定了你对 PDF 技术栈的理解方式,也决定了项目的工程成本。
PDF Viewer 的三层架构
在实际项目中,PDF Viewer 的实现可以拆成三个层次:
第一层:PDF.js Core(底层渲染引擎)
这是 PDF.js 提供的核心能力:解析 PDF 文件、Canvas 渲染、字体管理、页面布局计算。但它不提供任何 UI——你需要自己画工具栏、处理事件、管理缩放状态。
import * as pdfjsLib from 'pdfjs-dist'
const pdf = await pdfjsLib.getDocument(url).promise
const page = await pdf.getPage(1)
const viewport = page.getViewport({ scale: 1.5 })
// 然后手动 canvas 绘制...
这条路走得通,但离生产级还有十万八千里。
第二层:PDF.js Viewer(实际开发中的主流选择)
这是绝大多数项目的真实起点——直接使用 PDF.js 官方提供的 viewer.html:
<iframe src="/pdfjs-dist/web/viewer.html?file=/doc.pdf" width="100%" height="100%"></iframe>
它已经内置了:
- 完整的工具栏(缩放、翻页、下载、打印)
- 页面导航与缩略图
- 文本选择与搜索
- 基础的响应式布局
问题在于:在 React 项目里嵌入一个 iframe 意味着两个独立的运行时——React 和 Viewer 之间无法直接共享状态,自定义样式需要侵入修改 viewer 的 CSS,扩展业务 UI 需要 postMessage 通信。这在 Demo 阶段没问题,到了生产环境维护成本急剧上升。
第三层:应用级封装(如 InkLayer)
InkLayer 做的事情,本质上就是把第二层的 Viewer 能力原生接入 React 生态——不是替代 PDF.js,而是将 Viewer 的集成复杂度封装掉,暴露出 React 组件级别的 API。
React 中的 PDF Viewer 集成模式
如果你正在评估如何给 React 项目加一个 PDF Viewer,通常的路径是这样的:
方案 A:iframe 嵌入 viewer.html
function PdfViewerIframe({ url }) {
const viewerUrl = `/pdfjs/viewer.html?file=${encodeURIComponent(url)}`
return (
<iframe
src={viewerUrl}
style={{ width: '100%', height: '100vh' }}
title="PDF Viewer"
/>
)
}
- ✅ 最快出效果
- ❌ 状态割裂:React 不知道当前页码、缩放比例
- ❌ 定制困难:修改工具栏需要改 viewer.html 的源码
- ❌ 通信复杂:用 postMessage 在 iframe 内外传数据
方案 B:基于 PDF.js Core 自建
function PdfViewerCore({ url }) {
// 手动管理 canvas、缩放状态、翻页逻辑...
}
- ✅ 完全可控
- ❌ 开发周期长(通常 2-4 周起步)
- ❌ 需要深入理解 PDF.js 内部机制
- ❌ 性能优化、HiDPI 适配、内存管理都要自己踩坑
方案 C:使用 InkLayer
import { PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'
function App() {
return (
<PdfViewer
url="/doc.pdf"
layoutStyle={{ width: '100vw', height: '100vh' }}
/>
)
}
- ✅ 开箱即用
- ✅ React 原生状态同步
- ✅ 工具栏、侧边栏可定制
- ✅ 主题、暗色模式内置
→ 详见 React 集成文档 和 快速开始
InkLayer SDK:PDF.js Viewer 的 React 封装层
InkLayer 不是 PDF.js 的替代品,它是一个基于 PDF.js Viewer 的 React 封装层,定位在第二层和第三层之间:
- 保留 PDF.js Viewer 内置的渲染、搜索、文本选择能力
- 去掉 iframe 的隔离成本,将状态和 UI 暴露到 React 中
- 提供插槽(slot / render props)机制,让业务 UI 可以无缝嵌入
PdfViewer 组件能力速览
| 能力 | 说明 |
|---|---|
| 页面渲染 | 基于 PDF.js,多 Canvas 池化复用 |
| 翻页导航 | 支持滚动模式与分页模式 |
| 缩放控制 | 放大/缩小、适应页面、适应宽度 |
| 全文搜索 | 内置搜索面板 |
| 工具栏 | 可替换或扩展 |
| 侧边栏 | 支持自定义面板(缩略图、书签等) |
| 响应式 | 桌面端和移动端均适配 |
| 主题色 | 27 种色板,支持暗色模式 |
| 布局定制 | layoutStyle 控制容器尺寸 |
更多 API 详情请参考 React 文档。
对比总览
| 维度 | iframe | Core | InkLayer |
|---|---|---|---|
| 初期开发 | 快 | 慢 | 快 |
| 二次功能开发(批注/协作/AI) | ❌ 很痛苦 | ❌ 很重 | ✅ 可插拔 |
| 状态同步(页码/缩放/EventBus) | ❌ | ⚠️ 自建 | ✅ 内建 |
| PDF 扩展能力(annotation / overlay) | ❌ | ⚠️ | ✅(关键点) |
常见问题
React 中如何选择 PDF Viewer 方案?
如果只需快速展示 PDF 且不需要定制,iframe 嵌入 viewer.html 即可;如果需要 React 状态同步和 UI 扩展,推荐使用 InkLayer 等 React 封装层;仅当需要完全控制渲染流程时才考虑基于 PDF.js Core 自建。
PDF.js Viewer 和 PDF.js Core 有什么区别?
PDF.js Core 是底层渲染引擎,只提供 PDF 解析和 Canvas 绘制 API,不含任何 UI。PDF.js Viewer 是基于 Core 构建的完整查看器界面(viewer.html),包含工具栏、侧边栏、搜索等功能。
InkLayer 支持哪些框架?
InkLayer 目前提供 React 和 Vue 两个版本的 SDK,均基于 pdfjs-dist + Konva 构建,API 设计保持一致。
核心结论:如何选择 PDF Viewer 方案
在 React 中构建 PDF Viewer,本质上不是”使用 PDF.js”,而是:
基于 PDF.js Viewer 构建可集成的应用级 UI 系统。
你面对的真正问题不是 PDF 渲染——那已经被 PDF.js 解决了。真正的问题是:如何把 PDF.js Viewer 变成一个可嵌入 React 生态的组件,同时保留定制能力?
这正是 InkLayer 解决的问题。
开始使用 InkLayer
- 官网:inklayer.dev/zh-cn
- GitHub:github.com/Laomai-codefee/inklayer-react
- Starter 模板:github.com/Laomai-codefee/inklayer-react-starter
npm install inklayer-react
几分钟即可获得生产级 PDF Viewer。详见 快速开始文档。