纯前端就能搞定,但必须用PDF.js:需FileReader读ArrayBuffer,显式指定workerSrc,调getDocument({data: arrayBuffer})获取页数,逐页render到canvas,注意资源清理与性能优化。

PDF预览需要后端解析还是纯前端就能搞定
纯前端就能做,但有硬性限制:PDF.js 是唯一稳定可靠的方案。浏览器原生不支持 PDF 页面级 DOM 操作,<embed> 或 <iframe> 只能整份加载,无法提前获取页数、缩略图或单页 canvas 渲染——这些正是预览所有页面的前提。
别试 URL.createObjectURL(file) + <iframe src="blob:...">,它只会显示 PDF 查看器控件,你拿不到页码、不能截帧、也无法在上传前遍历每一页。
用 PDF.js 提前读取页数并逐页渲染到 canvas
核心流程是:选中文件 → 用 FileReader 读取为 ArrayBuffer → 传给 pdfjsLib.getDocument() → 调用 numPages 获取总页数 → 循环调用 getPage(i) + render() 输出到 <canvas>。
关键注意点:
立即学习“前端免费学习笔记(深入)”;
-
pdfjsLib.GlobalWorkerOptions.workerSrc必须显式指定,CDN 地址如"https://cdn.jsdelivr.net/npm/pdfjs-dist@3.4.120/build/pdf.worker.min.mjs",否则getDocument()直接失败且无提示 - 不要在
change事件里直接调render()所有页——页面多时会卡死 UI;应限制预览前 3–5 页,或加 loading 状态+取消机制 -
canvas.toDataURL("image/png")生成缩略图时,建议用scale: 0.3控制宽高,避免内存暴涨(尤其 A3 或扫描版 PDF)
常见报错和绕过方式
遇到 Unexpected server response (0) while retrieving PDF?这是 pdfjsLib.getDocument() 默认尝试网络请求导致的。必须传入 { data: arrayBuffer } 显式指定二进制源:
pdfjsLib.getDocument({ data: arrayBuffer }).promise.then(pdf => { ... })
还有两个高频坑:
- Chrome 119+ 对本地 file:// 协议禁用
fetch,若双击 HTML 文件打开,pdf.worker.min.mjs加载会失败——必须起本地服务(npx serve或 VS Code Live Server) - PDF 含加密或权限限制(如禁止复制)时,
getPage()可能 resolve 为空对象,需检查page?.render !== undefined再调用
预览弹窗结构怎么组织才不卡顿
弹窗本身用原生 <dialog> 最轻量,但 Safari 支持差;稳妥做法是绝对定位 <div class="preview-modal"> + overflow-y: auto 容器包裹 <canvas> 列表。
真实项目中容易被忽略的是资源清理:
- 每次新选文件前,必须调用上一个
pdf实例的destroy()方法,否则内存泄漏明显 - 每个
canvas渲染完立刻调ctx.clearRect()并丢弃不用的renderTask引用 - 用户关弹窗时,用
URL.revokeObjectURL()清掉createObjectURL生成的 blob URL(虽然这里没用到,但习惯要留)
页数超 20 的 PDF,预览逻辑里不做节流或分帧渲染,用户会以为页面卡死——这不是功能问题,是交互预期管理失效。



















