目录
- 介绍
- 问题
- 分析
- 解决
- 结束
介绍
先简单介绍下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内容 渲染页面到canvasawait page.render({canvasContext: context,viewport: viewport,})}})
}const createCanvasDom = (width, height) => {const canvas = document.createElement('canvas')// 设置canvas的宽度和高度canvas.width = widthcanvas.height = heightreturn 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')// 渲染页面到canvasawait page.render({canvasContext: context,viewport: viewport,})// 下一页 渲染 前创建 延时队列任务 延时时间可以自己调整 这里为了测试效果写了100ms 一般 100ms 效果就很OK了await sleep(100)}})
}const createCanvasDom = (width, height) => {const canvas = document.createElement('canvas')// 设置canvas的宽度和高度canvas.width = widthcanvas.height = heightreturn canvas
}const sleep = (time) => {return new Promise((resolve) => {setTimeout(() => {resolve(time)}, time)})
}
优化后效果如下:
2s 左右 pdf 第一页就出来了
后面基本 间隔 等待时间 后渲染下一页 表现如下:
结束
优化后 总渲染时间会变长 ,交互效果会更好
后面抽时间总结一篇 浏览器事件循环 的文章