一行代码优化 pdfjs 加载大文件的pdf 速度

目录

    • 介绍
    • 问题
    • 分析
    • 解决
    • 结束

介绍

先简单介绍下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 文档
});

还需要后端改动 ,还是前端自己来吧

分析

一行代码优化 pdfjs 加载大文件的pdf 速度_第1张图片
会发现 微任务 执行耗时太久 , 页面 没有发生render

这涉及到 一个 队列优先级的问题,粗略说下

一行代码优化 pdfjs 加载大文件的pdf 速度_第2张图片
优先级一般都是从上到下

我们可以得出结论 :
微任务执行 阻塞了 渲染(每一次微任务执行后 会创建下一页渲染的微任务 渲染进程 的 渲染主线程 一直会执行微任务队列里面的任务 会被一直占用)

解决

我们可以考虑在每一页 渲染的时候 (微任务)中间插入一个 比渲染队列低的任务 空出时间给 渲染主线程 去执行 渲染队列的 任务

可以 创建一个 延时队列的任务 主进程会执行渲染队列任务 后在执行延时队列任务
等待延时,后再创建 渲染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 第一页就出来了

后面基本 间隔 等待时间 后渲染下一页 表现如下:
一行代码优化 pdfjs 加载大文件的pdf 速度_第3张图片

结束

优化后 总渲染时间会变长 ,交互效果会更好

后面抽时间总结一篇 浏览器事件循环 的文章

你可能感兴趣的:(pdf)