介绍
先简单介绍下pdfjs 怎么 去加载pdf文件
import * as PDFJS from 'pdfjs-dist/legacy/build/pdf'
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
// blobUrl container指 dom 承载pdf 的容器
export const loadAndDisplayPdfByBlobUrl = (blobUrl, container) => {
// 加载PDF文件
PDFJS.getDocument(blobUrl).promise.then(async (pdf) => {
const totalPages = pdf.numPages
// 循环绘制每个页面
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
let page = await pdf.getPage(pageNum)
const viewport = page.getViewport({ scale: 1 })
const canvas = createCanvasDom(viewport.width, viewport.height)
container.appendChild(canvas)
const context = canvas.getContext('2d')
// 将每一页pdf内容 渲染页面到canvas
await page.render({
canvasContext: context,
viewport: viewport,
})
}
})
}
const createCanvasDom = (width, height) => {
const canvas = document.createElement('canvas')
// 设置canvas的宽度和高度
canvas.width = width
canvas.height = height
return canvas
}
简单 页面使用 (vue2 举例)
<template>
<div>
// 为了测试 自己上传pdf
<Uploader :after-read="afterRead" accept="*"></Uploader>
<div id="pdf"></div>
</div>
</template>
<script>
import { loadAndDisplayPdfByBlobUrl } from './handel'
import { Uploader } from 'vant'
export default {
components: {
Uploader,
},
data() {
return {
fileUrl: '',
}
},
mounted() {
loadAndDisplayPdfByBlobUrl()
},
methods: {
afterRead(file) {
// 将 file 对象 转化成 blobUrl
this.fileUrl = URL.createObjectURL(file.file)
loadAndDisplayPdfByBlobUrl(this.fileUrl, document.querySelector('#pdf'))
},
},
}
</script>
问题
上面的写法 处理体积小的 pdf文件 不会出现啥问题
当文件过大的时候 渲染量过大 很长一段时间界面会出现白屏 。用户体验不好
如下图:
这个js 忍者秘籍pdf 400多页 ,从上传后到 页面出现结果 消耗了大约 11s
全网最多的解决方案就是 传入的fileUrl 支持 分片下载,在开启 disableRange
export function getDocument(src: GetDocumentParameters): PDFDocumentLoadingTask;
export type GetDocumentParameters = string | URL | TypedArray | ArrayBuffer | PDFDataRangeTransport | DocumentInitParameters;
// 在 DocumentInitParameters 类型中有个 属性
/**
* - Disable range request loading of PDF
* files. When enabled, and if the server supports partial content requests,
* then the PDF will be fetched in chunks. The default value is `false`.
*/
disableRange?: boolean | undefined;
PDFJS.getDocument(url, {
disableRange: true
}).then(function(pdfDocument) {
// 处理 PDF 文档
});
还需要后端改动 ,还是前端自己来吧
分析
会发现 微任务 执行耗时太久 , 页面 没有发生render
这涉及到 一个 队列优先级的问题,粗略说下
优先级一般都是从上到下
我们可以得出结论 :
微任务执行 阻塞了 渲染(每一次微任务执行后 会创建下一页渲染的微任务 渲染进程 的 渲染主线程 一直会执行微任务队列里面的任务 会被一直占用)
解决
我们可以考虑在每一页 渲染的时候 (微任务)中间插入一个 比渲染队列低的任务 空出时间给 渲染主线程 去执行 渲染队列的 任务
可以 创建一个 延时队列的任务 主进程会执行渲染队列任务 后在执行延时队列任务
等待延时,后再创建 渲染pdf下一页的微任务。反复如此执行
最终代码如下
import * as PDFJS from 'pdfjs-dist/legacy/build/pdf'
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/legacy/build/pdf.worker.entry.js')
export const loadAndDisplayPdfByBlobUrl = (blobUrl, container) => {
// 加载PDF文件
PDFJS.getDocument(blobUrl).promise.then(async (pdf) => {
const totalPages = pdf.numPages
// 循环绘制每个页面
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
let page = await pdf.getPage(pageNum)
const viewport = page.getViewport({ scale: 1 })
const canvas = createCanvasDom(viewport.width, viewport.height)
container.appendChild(canvas)
const context = canvas.getContext('2d')
// 渲染页面到canvas
await page.render({
canvasContext: context,
viewport: viewport,
})
// 下一页 渲染 前创建 延时队列任务 延时时间可以自己调整 这里为了测试效果写了100ms 一般 100ms 效果就很OK了
await sleep(100)
}
})
}
const createCanvasDom = (width, height) => {
const canvas = document.createElement('canvas')
// 设置canvas的宽度和高度
canvas.width = width
canvas.height = height
return canvas
}
const sleep = (time) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(time)
}, time)
})
}
优化后效果如下:
2s 左右 pdf 第一页就出来了
后面基本 间隔 等待时间 后渲染下一页 表现如下:
结束
优化后 总渲染时间会变长 ,交互效果会更好
后面抽时间总结一篇 浏览器事件循环 的文章