vue3中如何使用pdfjs来展示pdf文档
在项目开发中碰到一个需求是在页面中展示pdf预览功能,本人的项目使用的是vue3,实现pdf预览使用的是pdf预览神器 pdfjs
以下,将详细介绍如何在项目中使用pdfjs,主要包括以下内容:
单页pdf加载
多页pdf加载
pdf放大/缩小/大小重置
pdf分页展示以及上下翻页
pdf添加水印
动态添加pdf
从服务端获取pdf文件
1. 准备工作
1.1 pdfjs-dist 安装:
npm i pdfjs-dist
2. 在vue3中使用pdfjs-dist查看pdf文档
2.1 基本页面代码
首先把基本的页面代码准备起来,具体代码如下:
<template> <div> <canvas id="pdf-canvas"></canvas> </div> </template> <script> import { Options, Vue } from 'vue-class-component' import * as PdfJs from 'pdfjs-dist/legacy/build/pdf.js' // 注意导入的写法 import Pdf from '@/assets/js.pdf' @Options({}) export default class SinglePage extends Vue { } </script>
2.2 pdfjs工作原理简述
pdfjs展示pdf文档的原理,实际上是将pdf中的内容解析,渲染到canvas中进行展示,因此我们使用pdfjs渲染出来的pdf文件,实际上是一张张canvas图片。
2.3 pdf文件展示(单页)
pdfjs的使用主要涉及到2个方法,分别是loadFile() 和 renderPage()
loadFile()
主要用来加载pdf文件,其实现如下:
loadFile (url: string): void { // 设定pdfjs的 workerSrc 参数 // NOTE: 这一步要特别注意,网上很多关于pdfjs的使用教程里漏了这一步,会出现workerSrc未定义的错误 // 这个pdf.worker.mjs在npm下载时,存在于node_modules/pdfjs-dist/build/pdf.worker.mjs PdfJs.GlobalWorkerOptions.workerSrc = '/printpdf/pdf.worker.mjs'; const loadingTask = PdfJs.getDocument(url) loadingTask.promise.then((pdf) => { this.pdfDoc = pdf // 保存加载的pdf文件流 this.pdfPages = this.pdfDoc.numPages // 获取pdf文件的总页数 this.$nextTick(() => { this.renderPage(1) // 将pdf文件内容渲染到canvas, }) }) }
以上代码因为使用了 ts ,有部分函数参数类型的设定,在使用过程中,如遇到ts的报错,可以直接把类型设置为 any.
需要注意:
以上部分的 workerSrc 赋值部分,需要特别注意,在pdfJs的使用示例中明确指出,
workerSrc 的值要手动指定,如果没有指定此值,则会出现 workerSrc 未定义的bug:
No "GlobalWorkerOptions.workerSrc" specified.
renderPage() 方法
主要用来将pdf文件的内容渲染到canvas上,其实现如下:
// num 表示渲染第几页 renderPage (num: any): void { this.pdfDoc.getPage(num).then((page: any) => { const canvas: any = document.getElementById('pdf-canvas') // 获取页面中的canvas元素 // 以下canvas的使用过程 const ctx: any = canvas.getContext('2d') const dpr = window.devicePixelRatio || 1 const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1 const ratio = dpr / bsr const viewport = page.getViewport({ scale: this.pdfScale }) // 设置pdf文件显示比例 canvas.width = viewport.width * ratio canvas.height = viewport.height * ratio canvas.style.width = viewport.width + 'px' canvas.style.height = viewport.height + 'px' ctx.setTransform(ratio, 0, 0, ratio, 0, 0) // 设置当pdf文件处于缩小或放大状态时,可以拖动 const renderContext = { canvasContext: ctx, viewport: viewport } // 将pdf文件的内容渲染到canvas中 page.render(renderContext) }) }
2.4 完整实现代码
<template> <div> <canvas id="pdf-canvas"></canvas> </div> </template> <script> import { Options, Vue } from 'vue-class-component' import * as PdfJs from 'pdfjs-dist/legacy/build/pdf.js' import Pdf from '@/assets/js.pdf' @Options({}) export default class SinglePage extends Vue { pdfDoc: any = '' // 保存加载的pdf文件流 pdfPages = 0 pdfScale = 1.0 // class 组件中生命周期函数直接这样写 created () { this.loadFile(Pdf) } loadFile (url: string): void { PdfJs.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.entry') const loadingTask = PdfJs.getDocument(url) loadingTask.promise.then((pdf) => { this.pdfDoc = pdf this.pdfPages = this.pdfDoc.numPages this.$nextTick(() => { this.renderPage(1) // 表示渲染第 1 页 }) }) } renderPage (num: any) { this.pdfDoc.getPage(num).then((page: any) => { const canvas: any = document.getElementById('pdf-canvas') const ctx: any = canvas.getContext('2d') const dpr = window.devicePixelRatio || 1 const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1 const ratio = dpr / bsr const viewport = page.getViewport({ scale: this.pdfScale }) canvas.width = viewport.width * ratio canvas.height = viewport.height * ratio canvas.style.width = viewport.width + 'px' canvas.style.height = viewport.height + 'px' ctx.setTransform(ratio, 0, 0, ratio, 0, 0) const renderContext = { canvasContext: ctx, viewport: viewport } page.render(renderContext) }) } } </script>
3.多页pdf加载
接下来记录如何实现多页pdf展示,
3.1 基本思路
多页的实现主要基于单页pdf。单页pdf中,renderPage传入的参数 num 正是pdf文档的页数。renderPage方法首先获取template中的canvas元素,然后从pdf文件中解析出第 num 页的内容,将pdf文件的内容渲染到canvas画布上。那么多页pdf只需要先根据pdf文档的页数,生成多个canvas画布,然后在渲染pdf文件的时候,只需要根据num去获取对应的 canvas 画布和对应的pdf文件内容,将pdf内容渲染到canvas上就可以了。在加载pdf文件的时候,从第1页开始渲染,然后递归调用渲染函数,在每一次调用渲染函数的末尾,都将 num 的值加1,然后继续调用renderPage方法,直到所有的pdf页面渲染完毕为止。
下面看下具体的代码实现:
3.2 实现代码
<template> <div> <!--此处根据pdf的页数动态生成相应数量的canvas画布--> <canvas v-for="pageIndex in pdfPages" :id="`pdf-canvas-`+pageIndex" :key="pageIndex"></canvas> </div> </template> <script> import { Options, Vue } from 'vue-class-component' import * as PdfJs from 'pdfjs-dist/legacy/build/pdf.js' import Pdf from '@/assets/js.pdf' @Options({}) export default class MultiPage extends Vue { pdfDoc: any = '' // 保存加载的pdf文件流 pdfPages = 0 // pdf文件的页数 pdfScale = 1.0 // 缩放比例 // class 组件中生命周期函数直接这样写 created () { this.loadFile(Pdf) } loadFile (url: string): void { PdfJs.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.entry') const loadingTask = PdfJs.getDocument(url) loadingTask.promise.then((pdf) => { this.pdfDoc = pdf this.pdfPages = this.pdfDoc.numPages this.$nextTick(() => { this.renderPage(1) // 表示渲染第 1 页 }) }) } renderPage (num: any) { this.pdfDoc.getPage(num).then((page: any) => { const canvasId = 'pdf-canvas-' + num // 第num个canvas画布的id const canvas: any = document.getElementById(canvasId) const ctx: any = canvas.getContext('2d') const dpr = window.devicePixelRatio || 1 const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1 const ratio = dpr / bsr const viewport = page.getViewport({ scale: this.pdfScale }) canvas.width = viewport.width * ratio canvas.height = viewport.height * ratio canvas.style.width = viewport.width + 'px' canvas.style.height = viewport.height + 'px' ctx.setTransform(ratio, 0, 0, ratio, 0, 0) const renderContext = { canvasContext: ctx, viewport: viewport } page.render(renderContext).promise.then(function() { // 在第num页渲染完毕后,递归调用renderPage方法,去渲染下一页,直到所有页面渲染完毕为止 if (num < this.pdfPages) { this.renderPage(num + 1) } }) }) } } </script>