优雅地在网页中嵌入pdf且适配移动端浏览器
一、不需要考虑移动端显示效果的方法
1、使用对象数据嵌入pdf
<object data="https://zzhanzhang.top/YOU_PDF.pdf" height="800px" width="100%"></object>
优缺点:语法简洁,无需额外依赖,支持翻页缩放基础交互;但移动端适配差可控性低,安卓浏览器直接下载pdf文件了。
2、典中典<iframe>
<iframe>通过创建独立窗口加载 PDF,本质与<object>类似,都是依赖浏览器原生渲染。
<iframe src="https://zzhanzhang.top/YOU_PDF.pdf" height="800px" width="100%"></iframe>
优缺点:与主页面隔离,避免 PDF 渲染影响页面其他元素,与<object>一样简洁,但移动端问题与<object>完全一致:渲染错乱、会触发直接下载;
3、<embed>标签嵌入
<embed>是早期用于嵌入多媒体资源的标签,功能与<object>高度重叠,现在已较少单独使用。
<embed src="https://zzhanzhang.top/YOU_PDF.pdf" type="application/pdf" width="100%" height="800px" />
优缺点:移动端适配问题更严重:很多端浏览器会直接忽略<embed>标签,显示空白;兼容性最差。
二、借助第三方工具嵌入pdf并且自适应移动端浏览器
这里借助smallpdf这个在线工具生成一个容器可以放在网页中的任何部分,并且会自适应移动端浏览器的显示效果(本质是用 JS 封装了渲染逻辑)
<div
style="width: 100%; height: 580px; max-width: 1200px; margin: 0 auto;" <!-- 加max-width避免桌面端过宽 -->
class="smallpdf-widget"
data-pdf-url="https://zzhanzhang.top/YOU_PDF.pdf"
data-download="false" <!-- 可选:是否显示下载按钮(true/false) -->
data-print="false" <!-- 可选:是否显示打印按钮(true/false) -->
></div>
<!-- 引入SmallPDF的渲染脚本 -->
<script src="https://smallpdf.com/api/embed-widget.js"></script>
如图:
优点:
- 移动端会自动隐藏多余工具栏,仅保留翻页和缩放按钮;桌面端显示完整控件
- 无需写 JS 逻辑,配置属性即可自定义功能;
- 自动响应屏幕宽度,控件布局随设备调整;
- 支持基础自定义,可隐藏下载 / 打印按钮。
缺点:
- 若 SmallPDF 的 CDN 故障,PDF 将无法加载;
- 无法自定义主题色、无法监听页面切换事件;
- 查看器会带 SmallPDF 水印。
三、高级玩法,使用PDF.js达到完美的显示效果
pdf.js简介:它是 Mozilla 开源的 PDF 渲染库,完全基于 JS 实现(不依赖浏览器原生能力),可精准控制移动端渲染逻辑。
这里需要引入PDF.js库和PDF工作者(worker):
pdf.min.js:主库,负责 PDF 解析和渲染逻辑;pdf.worker.min.js:工作线程,负责异步解析 PDF(避免阻塞主线程)。
以下代码已优化移动端体验:自动适配屏幕宽度、支持垂直滚动、优化文本清晰度;
<div id="pdfContainer" style="width: 100%; height: 100vh; overflow-y: auto; overflow-x: hidden;"></div>
<!-- PDF.js库 -->
<script src="https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.min.js"></script>
<script>
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc =
'https://unpkg.com/pdfjs-dist@3.4.120/build/pdf.worker.min.js';
const url = 'https://zzhanzhang.top/YOU_PDF.pdf';
const container = document.getElementById('pdfContainer');
const devicePixelRatio = window.devicePixelRatio || 1;
const loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(pdf => {
const totalPages = pdf.numPages;
for(let pageNum = 1; pageNum <= totalPages; pageNum++) {
pdf.getPage(pageNum).then(page => {
const canvas = document.createElement('canvas');
canvas.id = `pdfCanvas-page-${pageNum}`;
// 获取设备像素比
const viewportWidth = window.innerWidth;
const viewport = page.getViewport({ scale: 1.0 });
const scale = (viewportWidth / viewport.width) * devicePixelRatio;
const adaptedViewport = page.getViewport({ scale: scale });
canvas.width = adaptedViewport.width;
canvas.height = adaptedViewport.height;
canvas.style.width = '100%';
canvas.style.height = 'auto';
canvas.style.display = 'block';
canvas.style.margin = '0 auto';
container.appendChild(canvas);
// 渲染页面
const renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: adaptedViewport,
imageSmoothingEnabled: true,
textLayer: true
};
page.render(renderContext);
});
}
});
</script>
优点:
- 100% 可控,可自定义工具栏(如添加页码导航、缩放按钮)、主题色、批注功能;
- 移动端和桌面端渲染效果完全一致,无浏览器差异;
- 可下载库到本地部署,不依赖第三方服务;
缺点:
- 需手动处理渲染逻辑、错误捕获、交互优化;
- 若 PDF 文件与网页不同域名,需要解决跨域问题;
- 针对大文件需自己实现分片加载或进度条(默认是一次性加载整个 PDF)。
四、版本pdfjs-dist@3.4.120遇到的一个渲染错位问题
问题 1:并发渲染导致资源错误
错误信息:Uncaught (in promise) Error: Requesting object that isn't
resolved yet pattern_p0_91.
原因分析:当使用for循环并发调用pdf.getPage()时,多个页面同时渲染会导致
PDF 内部资源(如 Type3 字体、图案 pattern)竞争。某些资源还没完全加载就被引用,从而抛出错误。
解决方案:使用 async/await 串行渲染
loadingTask.promise.then(async pdf => {
const totalPages = pdf.numPages;
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
try {
const page = await pdf.getPage(pageNum);
// ... 创建 canvas 和配置 viewport ...
const renderContext = {
canvasContext: canvas.getContext('2d', { alpha: false }),
viewport: adaptedViewport,
imageSmoothingEnabled: true
};
await page.render(renderContext).promise; // 等待渲染完成
} catch (error) {
console.warn(`第${pageNum}页渲染失败:`, error);
}
}
});
关键点:
- 使用
async/await确保上一页渲染完成后再渲染下一页; - 添加
try-catch捕获单页错误,避免中断整个流程; - 移除无效参数
textLayer: true(PDF.js 会忽略它)。
问题 2:requestAnimationFrame 性能警告
警告信息:[Violation] 'requestAnimationFrame' handler took
<N>ms
原因:PDF 渲染计算量大,尤其是包含复杂图形或高分辨率图片时,单次渲染可能超过 50ms,触发浏览器性能警告。
优化方案:
- 降低渲染质量:将 scale 乘以 0.8,减少 20% 计算量;
- 添加页间延迟:每页渲染后等待 100ms,让浏览器处理其他任务;
- 移除 alpha 优化:某些 PDF 与
getContext('2d', { alpha: false })不兼容,改回标准getContext('2d')。
问题 3:PDF.js 版本升级
如果上述方案仍无法解决,考虑升级到最新版 PDF.js(如 5.4.394),新版本修复了大量渲染 bug 和性能问题。
升级步骤:
<!-- HTML 中使用 ES Module 导入 -->
<script type="module">
import * as pdfjsLib from './js/pdfjs-dist@5.4.394/files/build/pdf.min.mjs';
window['pdfjs-dist/build/pdf'] = pdfjsLib;
</script>
<!-- JS 中配置 worker -->
<script>
const pdfjsLib = window['pdfjs-dist/build/pdf'];
pdfjsLib.GlobalWorkerOptions.workerSrc = 'js/pdfjs-dist@5.4.394/files/build/pdf.worker.min.mjs';
// 添加加载参数增强稳定性
const loadingTask = pdfjsLib.getDocument({
url: url,
useSystemFonts: true, // 使用系统字体,减少下载
disableRange: false,
disableStream: false,
disableAutoFetch: false,
isEvalSupported: true
});
</script>
注意:ES Module 路径必须以./、../或/开头,否则会报错Failed
to resolve module specifier。
五、我们还能做的其它事情
- 压缩文件大小(用 SmallPDF、Adobe Acrobat 压缩),避免移动端加载过慢;
- 大 PDF(超过 20 页)可实现「懒加载」(只渲染可视区域的页面),减少初始加载时间;