这篇文章主要介绍了如何在 Vue 中集成 Mozilla/PDF.js ,实现自定义的 PDF 预览器,以及给被预览的 PDF 添加水印

可用插件介绍
Mozilla 提供了 PDF.js 和pdfjs-dist ,两者的区别如下:
PDF.js ,一个完整的 PDF 查看器,可以直接使用其提供的 viewer.html 查看 PDF 内容,包含完整样式和相关功能。优点是快速集成,不需要自己实现查看器的功能和样式。缺点是如果要自定义样式和功能,反而会很麻烦。
pdfjs-dist ,PDF.js 的预购建版本,只包含 PDF 内容的渲染功能,需要自己实现查看器的样式和相关功能。
Vue 官方插件库 Awesome Vue.js 推荐的vue-pdf 就是对 pdfjs-dist 进行了封装实现,一般情况下使用 vue-pdf 即可快速实现 PDF 的预览效果。
根据需求进行插件选型
我们的需求是在现有页面中实现 PDF 预览的同时,在 PDF 内容上添加水印。
PDF.js 这种完整版的查看器显得过于臃肿,而 vue-pdf 虽然可以快速实现预览效果,但在添加水印时需要对显示 PDF 的 canvas 进行二次渲染,经过尝试后发现会抛出 Failed to execute 'drawImage' on 'CanvasRenderingContext2D': Overload resolution failed. 的错误。
所以最后选择直接集成 pdfjs-dist 来完成全部功能
安装和引入插件
安装:
yarn add pdfjs-dist
引入:
必须手动指定 workerSrc ,不然会抛出 Setting up fake worker failed 的错误。
虽然本地目录 node_modules/pdfjs-dist/build/pdf.worker.js 存在该文件,但实际引入时依旧会报错,所以只能使用 CDN 地址下的 pdf.worker.js 。可以通过传入 PDFJS.version 来提高引入的灵活性。
import * as PDFJS from 'pdfjs-dist'PDFJS.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${PDFJS.version}/pdf.worker.js'初始化插件
用于渲染内容的 canvas 节点
<canvas id="pdfCanvas"></canvas>
用于接收 PDFJS 实例的对象
props: { // PDF 文件的实际链接 url: { type: String }},data () { return { totalPage: 1, // PDFJS 实例 pdfDoc: null }},methods: { _initPdf () { PDFJS.getDocument(this.url).promise.then(pdf => { // 文档对象 this.pdfDoc = pdf // 总页数 this.totalPage = pdf.numPages // 渲染页面 this.$nextTick(() => { this._renderPage() }) }) }}监听链接变化并初始化实例
当外部传入的 url 有效时,就可以触发 PDF 查看器的初始化函数
watch: { 'url' (val) { if (!val) { return } this._initPdf() }},渲染 PDF 内容
获取当前页面比率,用于计算内容的实际宽高
methods: { _getRatio (ctx) { let dpr = window.devicePixelRatio || 1 let bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1 return dpr / bsr }}渲染当前页面
page.getViewport({ scale }) 中的 scale 非常关键,直接关系到渲染出来的内容能不能撑满整个父容器,所以这里分别获取了父容器和页面本身的宽度,父容器宽度 / 页面宽度 后得出的比率就是实际页面需要放大多少的比率。
page.view 是一个数组,里面有四个值,分别是 x轴偏移量、y轴偏移量、宽度、高度。 要获取真实的宽度,还需要考虑当前页面比率,所以使用 page.view[2] * ratio 计算得出实际宽度。
data () { return { currentPage: 1, totalPage: 1, width: 0, height: 0, pdfDoc: null }},methods: { _renderPage () { this.pdfDoc.getPage(this.currentPage).then(page => { let canvas = document.querySelector('#pdfCanvas') let ctx = canvas.getContext('2d') // 获取页面比率 let ratio = this._getRatio(ctx) // 根据页面宽度和视口宽度的比率就是内容区的放大比率 let dialogWidth = this.$refs['pdfDialog'].$el.querySelector('.el-dialog').clientWidth - 40 let pageWidth = page.view[2] * ratio let scale = dialogWidth / pageWidth let viewport = page.getViewport({ scale }) // 记录内容区宽高,后期添加水印时需要 this.width = viewport.width * ratio this.height = viewport.height * ratio canvas.width = this.wi

