PDFJS优化:PDF文件分片加载
作者:linkyang
标签:pdf
发布时间:2025年05月06日 22:58:45
更新时间:2025年05月06日 22:58:45
一、为什么需要使用分片加载?
- 提高加载速度:将 PDF 文件分成多个小片后,浏览器可以同时加载多个片段,利用多线程或并行加载的机制,加快整体的加载过程。尤其是对于较大的 PDF 文件,用户无需等待整个文件完全下载后才开始查看,能更快地看到文件的部分内容,节省等待时间。
- 改善用户体验:分片加载允许用户在文件尚未完全加载时就开始浏览已加载的部分,对于长文档或包含大量图片、图表的 PDF,用户可以立即开始阅读文本内容,而不必面对长时间的空白页面或加载进度条,提升阅读体验。
- 适应不同网络环境:在网络不稳定或带宽有限的情况下,分片加载更为可靠。如果某个片段加载失败,可以单独重新加载该片段,而不是重新下载整个文件,降低了因网络问题导致加载失败的风险,提高了文件加载的成功率。
- 节省内存:一次性加载整个大型 PDF 文件可能会占用大量内存,导致浏览器或应用程序运行缓慢甚至卡顿。分片加载则是按需加载,每次只在内存中存储当前需要显示的片段,减轻了内存压力,使系统能够更高效地运行,尤其对于移动设备或性能有限的计算机,这一点更为重要。
二、PDFJS分片相关核心配置参数
配置项 | 类型 | 默认值 | 功能说明 |
---|---|---|---|
rangeChunkSize | number | 65536 | 定义分片请求的字节粒度 |
disableStream | boolean | false | 禁用TCP流式传输 |
disableAutoFetch | boolean | false | 关闭PDF.js的预加载机制,仅加载可视区域内容 |
三、分片加载触发机制
- 首次请求协商
当PDF.js发起初始请求时,服务端需在响应头中声明:http
Content-Length: 1234567 // 完整文件字节数 Accept-Ranges: bytes // 声明支持字节范围请求
此举将激活PDF.js的分片请求模式 - 动态范围请求
后续请求将携带Range头实现精准加载:http
GET /document.pdf Range: bytes=0-1048575 // 请求前1MB数据
三、开发注意事项
- 避免一次性渲染所有pdf页面javascript
// 错误做法:触发全部分片请求 Array(pdf.numPages).forEach(renderPage); // 正确做法:基于IntersectionObserver动态加载 observer.observe(pageContainer);
- 服务端必须实现的功能
- 正确处理
HEAD
请求返回Content-Length
和Accept-Ranges
- 支持
Range
请求的合法性校验(HTTP 416状态码处理)
- 正确处理
四、代码案例
前端相关配置
js
const loadingTask = pdfjsLib.getDocument({
url: '',//文档请求地址
httpHeaders: {}, //自定义请求头,例如可以携带token
disableAutoFetch: true, //是否禁用预加载
disableStream: true,//是否禁用流
rangeChunkSize: 1024 * 1024 * 2,//文件分片大小
cMapUrl: '/cmaps/',
cMapPacked: true,
})
后端接口实现
我这里后端使用的是eggjs,其他框架和编程语言按照这个思路去实现就行。
js
// PDF分片预览
async previewFile(fileId) {
const { ctx } = this
//从数据库中查询
const file = await ctx.model.FileTable.findOne({
where: {
fileId,
},
})
if (!file) {
throw new Error('文件不存在')
}
const filePath = file.originalPath ? path.join('app', file.originalPath) : path.join('app', file.filePath)
// 检查文件是否存在
if (!fs.existsSync(filePath)) {
ctx.status = 404
ctx.body = { error: '文件路径不存在' }
return
}
const stat = fs.statSync(filePath) // 获取文件信息
const total = stat.size // 文件总大小
const range = ctx.get('range') // 获取请求头中的 Range
if (range) {
// 解析 Range 请求头
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : total - 1
// 检查范围合法性
if (start >= total || end >= total) {
ctx.status = 416 // Range Not Satisfiable
ctx.set('Content-Range', `bytes */${total}`)
return
}
// 设置响应头
ctx.status = 206 //必须设置206,代表返回的是部分资源
ctx.set('Content-Range', `bytes ${start}-${end}/${total}`)
ctx.set('Accept-Ranges', 'bytes')
ctx.set('Content-Length', end - start + 1)
ctx.set('Content-Type', 'application/pdf')
// 返回文件段
ctx.body = fs.createReadStream(filePath, { start, end })
} else {
// 设置响应头,告诉前端后台支持文件分片
ctx.set('Content-Length', total) //必须设置
ctx.set('Accept-Ranges', 'bytes')//必须设置
ctx.set('Content-Type', 'application/pdf')
ctx.set('Content-Range', `bytes 0-${total - 1}/${total}`) // 返回文件总大小
ctx.set('Access-Control-Expose-Headers', 'Accept-Ranges,Content-Range')
ctx.body = fs.createReadStream(filePath) // 返回整个文件流
}
}
五、关于分片后还是请求了大量的数据
在官方仓库的Issues中有人提出了这个问题,这个问题官方在这个这个更新补丁中也提到了。有些pdf文件的页数有可能是不正确的,这样会导致pdf在解析的时候异常中断并导致浏览器异常挂起。所以官方更新这个补丁在打开pdf的时候检查了最后一页来确保pdf的页数是正确的,所以需要加载更多的数据。也会减慢pdf加载的速度。
如果可以保证PDF文件的完整性可以使用2.3.200
或以下的版本,就是没有包含这个补丁的版本,实际测试的确速度更快,也可以使用qpdf对pdf文件进行线性化优化,也可以优化加载速度。
登录后可查看并参与评论
Gitee 登录
目录导航
一、为什么需要使用分片加载?
二、PDFJS分片相关核心配置参数
三、分片加载触发机制
三、开发注意事项
四、代码案例
前端相关配置
后端接口实现
五、关于分片后还是请求了大量的数据
友情链接
暂无链接