Appearance
pdf在线预览,实现文字可选择(深度研究)
在项目里做 PDF 在线预览,看起来是个很普通的需求。但只要你做过复杂的业务场景(比如电子合同、发票预览、公文系统),就会发现这东西里头的坑真不少:用户抱怨文字选不中没法复制、看合同发现盖的红章莫名其妙消失了、或者换个手机打开直接提示下载文件。
市面上“PDF 预览”的开源库挺多的,但这中间到底有什么区别?该怎么选?
试过一圈之后,我最后的结论依然是:直接用 Mozilla 的 PDF.js,而且最好直接上最新的 v6 版本。
一、 市面上常见 PDF 预览方案的坑
在动手写代码之前,先看看大家常用的几种方案和各自的硬伤:
PDFObject / 直接用
<embed>或<iframe>嵌入- 原理:调用浏览器自带的 PDF 插件。
- 坑点:PC 端 Chrome 看着挺好,但移动端(iOS / Android)几乎全军覆没,很多手机浏览器根本不帮你内嵌预览,而是直接弹下载。而且你没办法自定义任何 UI 界面。
后端转图片 / 纯 Canvas 绘制渲染
- 原理:服务端把每一页 PDF 转成图片发给前端,或者前端用简易 Canvas 画出来。
- 坑点:最致命的问题是文字根本选不上,没法复制,也无法 Ctrl+F 搜索。除了防截图防复制的特殊场景外,这种体验用户分分钟要吐槽。
pdf2htmlEX
- 原理:服务端用 C++ 把 PDF 精确转成 HTML 和 CSS。
- 坑点:效果虽然不错,但需要你在后端跑一个 C++ 的转换服务,遇到几百页的大 PDF 服务端 CPU 直接飚满,没办法做到纯前端随载随看。
各种 React / Vue 的包装库 (如 vue-pdf、react-pdf 等)
- 原理:基于 PDF.js 套了一层框架组件。
- 坑点:这些第三方封装库更新极慢,经常绑死的是两三年前的老版本 PDF.js。老版本里的各种印章丢失、中文乱码 Bug 它们一个没少,出了问题你还很难改。
二、 为什么推荐直接用 PDF.js v6?
有些开发者早期用过 PDF.js 的旧版本,觉得它配置繁琐或者有 Bug。但到了 PDF.js v6 版本,官方修复和重构了几个非常核心的痛点。
1. 真正的文字可选择与搜索 (Text Layer)
有些库预览 PDF 只是给用户“看个图”,但 PDF.js 的做法是双层渲染:
- 底层用 Canvas 绘制清晰的图形和排版;
- 上层用绝对定位生成一层完全透明的 HTML 文本层(TextLayer),刚好重合在 Canvas 上面。
在 v6 版本里,官方重构了 Text Layer 的字体测量和 DOM 节点重排算法。现在选文本拖拽极其顺滑,支持跨行选中、复制、直接 Ctrl + F 全文高亮搜索,体验和直接看常规网页几乎没区别。
2. 解决了讨厌的电子盖章/印章丢失问题
做合同或发票预览时,经常遇到页面内容都在,但右上角的红章不见了,或者变成了黑色方块。
这是因为 PDF 里的印章通常属于 Stamp Annotation(盖章注释),并且带透明度混合与自定义字体。旧版 PDF.js 对这类注释处理得很粗暴,经常直接跳过。
PDF.js v6 重构了 AnnotationLayer,不仅把矢量印章、透明度渐变完美叠加了出来,还修复了盖章周围出现“白块”的尴尬 Bug。
3. Worker + WebAssembly 性能更好
解析 PDF 是个很吃 CPU 的活。v6 版本把文件解密、图像解码这些耗时计算全部扔进了 pdf.worker.js,结合 WebAssembly 跑在后台线程,就算加载几百页的大文档,主线程也不会被卡死掉帧。
三、 实际开发中的几个避坑点
要用好 PDF.js v6,这几个地方少走弯路:
- 别用老旧的 Wrapper 库:直接用官方 npm 包
pdfjs-dist(v6),或者直接把官方编译好的viewer.html拿来用iframe嵌入,最稳定。 - 一定要配 CMap 资源:如果你的 PDF 包含中文、日文等字体,必须把
cmaps/静态资源目录打包进去,并在代码里指定cMapUrl。漏掉这个,中文字体或者印章文字就会变乱码。 - 记得开启这两个参数:手写渲染逻辑时,务必加上
renderTextLayer: true和renderAnnotationLayer: true,不然文字选不上、印章也显示不出来。
四、 总结
总结下来,在 Web 端做 PDF 预览,不用去搞那些花里胡哨的第三方封装。
如果你需要文字能选中复制、能 Ctrl+F 搜索、合同里的红章能正常显示,目前最省心、最靠谱的选择就是直接上 PDF.js v6。虽然刚开始配置 Worker 和 CMap 资源要花一点时间,但相比后续去处理各种印章不见了、中文变乱码的客诉,这绝对是最接地气、最靠谱的落地方案。