返回博客

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

~4 分钟阅读
InkLayer

在 Vue 中实现一个 PDF Viewer,看似简单,但在真实工程中通常涉及一个关键事实:

Vue PDF Viewer 的工程实现通常分为三种主流路径:iframe 嵌入 PDF.js Viewer、自建 PDF.js 渲染层,以及基于 PDF.js Viewer 的 Vue 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>

它已经内置了:

  • 完整的工具栏(缩放、翻页、下载、打印)
  • 页面导航与缩略图
  • 文本选择与搜索
  • 基础的响应式布局

问题在于:在 Vue 项目里嵌入一个 iframe 意味着两个独立的运行时——Vue 和 Viewer 之间无法直接共享状态,自定义样式需要侵入修改 viewer 的 CSS,扩展业务 UI 需要 postMessage 通信。这在 Demo 阶段没问题,到了生产环境维护成本急剧上升。

第三层:应用级封装(如 InkLayer)

InkLayer 做的事情,本质上就是把第二层的 Viewer 能力原生接入 Vue 生态——不是替代 PDF.js,而是将 Viewer 的集成复杂度封装掉,暴露出 Vue 组件级别的 API。


Vue 中的 PDF Viewer 集成模式

如果你正在评估如何给 Vue 项目加一个 PDF Viewer,通常的路径是这样的:

方案 A:iframe 嵌入 viewer.html

<template>
  <iframe
    :src="viewerUrl"
    style="width: 100%; height: 100vh"
    title="PDF Viewer"
  />
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({ url: String })
const viewerUrl = computed(() =>
  `/pdfjs/viewer.html?file=${encodeURIComponent(props.url)}`
)
</script>
  • ✅ 最快出效果
  • ❌ 状态割裂:Vue 不知道当前页码、缩放比例
  • ❌ 定制困难:修改工具栏需要改 viewer.html 的源码
  • ❌ 通信复杂:用 postMessage 在 iframe 内外传数据

方案 B:基于 PDF.js Core 自建

<template>
  <canvas ref="canvasRef" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
// 手动管理 canvas、缩放状态、翻页逻辑...
// 通常 2-4 周起步
</script>
  • ✅ 完全可控
  • ❌ 开发周期长(通常 2-4 周起步)
  • ❌ 需要深入理解 PDF.js 内部机制
  • ❌ 性能优化、HiDPI 适配、内存管理都要自己踩坑

方案 C:使用 InkLayer

<template>
  <PdfViewer
    url="/doc.pdf"
    :layoutStyle="{ width: '100vw', height: '100vh' }"
  />
</template>

<script setup>
import { PdfViewer } from 'inklayer-vue'
import 'inklayer-vue/style'
</script>
  • ✅ 开箱即用
  • ✅ Vue 原生状态同步
  • ✅ 工具栏、侧边栏可定制
  • ✅ 主题、暗色模式内置

→ 详见 Vue 集成文档快速开始


InkLayer SDK:PDF.js Viewer 的 Vue 封装层

InkLayer 不是 PDF.js 的替代品,它是一个基于 PDF.js Viewer 的 Vue 封装层,定位在第二层和第三层之间:

  • 保留 PDF.js Viewer 内置的渲染、搜索、文本选择能力
  • 去掉 iframe 的隔离成本,将状态和 UI 暴露到 Vue 中
  • 提供插槽(slot)机制,让业务 UI 可以无缝嵌入

PdfViewer 组件能力速览

能力说明
页面渲染基于 PDF.js,多 Canvas 池化复用
翻页导航支持滚动模式与分页模式
缩放控制放大/缩小、适应页面、适应宽度
全文搜索内置搜索面板
工具栏可替换或扩展
侧边栏支持自定义面板(缩略图、书签等)
响应式桌面端和移动端均适配
主题色27 种色板,支持暗色模式
布局定制layoutStyle 控制容器尺寸

更多 API 详情请参考 Vue 文档


对比总览

维度iframeCoreInkLayer
初期开发
二次功能开发(批注/协作/AI)❌ 很痛苦❌ 很重✅ 可插拔
状态同步(页码/缩放/EventBus)⚠️ 自建✅ 内建
PDF 扩展能力(annotation / overlay)⚠️✅(关键点)

常见问题

Vue 中如何选择 PDF Viewer 方案?

如果只需快速展示 PDF 且不需要定制,iframe 嵌入 viewer.html 即可;如果需要 Vue 状态同步和 UI 扩展,推荐使用 InkLayer 等 Vue 封装层;仅当需要完全控制渲染流程时才考虑基于 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 方案

在 Vue 中构建 PDF Viewer,本质上不是”使用 PDF.js”,而是:

基于 PDF.js Viewer 构建可集成的应用级 UI 系统。

你面对的真正问题不是 PDF 渲染——那已经被 PDF.js 解决了。真正的问题是:如何把 PDF.js Viewer 变成一个可嵌入 Vue 生态的组件,同时保留定制能力?

这正是 InkLayer 解决的问题。


开始使用 InkLayer

npm install inklayer-vue

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

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

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