返回博客

如何在 React 中构建 PDF Viewer(基于 PDF.js Viewer 与 InkLayer SDK)

~4 分钟阅读
InkLayer

在 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 文档


对比总览

维度iframeCoreInkLayer
初期开发
二次功能开发(批注/协作/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 目前提供 ReactVue 两个版本的 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

npm install inklayer-react

几分钟即可获得生产级 PDF Viewer。详见 快速开始文档

准备好构建 PDF 批注功能了吗?

InkLayer provides a complete PDF annotation SDK for React & Vue — 14 annotation types, pixel-perfect rendering, and one-command setup.