阅读 997

canvas结合pdfjs-dist在vue项目中预览pdf文件

前言

公司项目,需要在app的内嵌H5项目中,实现预览pdf文件的需求

思考

这个需求拿到手上的一瞬间,我是懵的,因为这是一个我从来没有涉足过的领域,故有了一下的一些思考:
1.预览pdf应该是有插件的,先搜索一波能在vue中使用的插件:结果有vue-pdf,pdfjs
2.再明确一下需求:可能会有多个pdf文件,每个pdf文件可能不止一页,那么,至少在第一层肯定是循环了

实践

最开始,是打算用vue-pdf的,这个玩意儿其实我也根本不会使用。但是吧,有搜索这个方便的工具在,这个问题都不是问题。于是乎,开始一波疯狂的搜索行为,然而,理想很丰满,现实很骨感,搜索到的方法,我没一个成功实现了需求的。
既然这个不行,那就再试试pdfjs这个最原始的吧(vue-pdf似乎就是基于这个弄出来的)。按照教程方法,先下载一波文件,然后解压,放到项目中。一顿操作猛如虎,一看输入只有5,哭泣。
没办法,继续百度找方法,最后找到了另外一种pdfjs的引入方式,即pdfjs-dist,我也不知道这个和pdfjs有啥不一样的,貌似就是一个东西。
看了下这个,不用下载源代码放到项目中,似乎操作也比较简单,于是再次开始实践

操作

第一步:引入

npm i pdfjs-dist 复制代码

其实这里也有个坑,我开始下载了最新版本的,但是出现了蜜汁bug,后面看了前辈大佬们的文章,找了一个指定版本2.2.228
第二步:使用 看了很多文章,都是结合了canvas来使用的,所以我也不会开辟一个新的使用方法(我不会啊,想开也开不动)

// html部分  <template v-if="pdfList.length > 0">       <div v-for="(pdfPages, index) in pdfList" :key="index" class>         <canvas           v-for="page in pdfPages"           :key="page"           :id="'pdfCanvas' + page"         ></canvas>       </div>     </template> 复制代码

// 1.引入pdfjs import PDFJS from "pdfjs-dist"; created() {     PDFJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.min.js");     // 不要问我为啥在这里写,我表示不是很清楚,我最开始也是看文章中写在import下面的,但是我试了下不行,所以试了一下写在这里,结果试ok的 } 复制代码

最开始,我并没有封装canvas部分,全部写在了一个组件中,除了造成组件代码量庞大以外,此时还未出现其他问题

// 请求接口,获取pdf文件流(数据是第三方的,而且还是个明文的链接,数据敏感,考虑到安全性,我们这边是不存服务器的,后端只是转发而已) async getFileUrl() {       try {         const res = await this.$http.get(url);         console.log(res, "getFileUrl");         if (xxx.code === 200) {           let urlList = xxx.data;           urlList.forEach(item => {             this.handlerUrl(item);           });           return;         }       } catch (error) {         console.log(error, "getFileUrl");       }     },     // 数据除了pdf,还有ofd的,先忽略ofd吧     handlerUrl(item) {       if (item.fileFormat === "PDF") {         this.getPdfFile(item.licenseId);       } else if (item.fileFormat === "OFD") {         this.getOfdFile(item.licenseId);       }     }, 复制代码

接下来就是渲染pdf文件了

async getPdfFile(licenseId) {       const url = `xxx`;       try {         const res = await this.$http({           url,           method: "get",           params: {             licenseId           },           responseType: "blob"         });         // 将文件流处理一下,变成url         if (res.status == 200) {           const content = res.data;           let file = new Blob([content], {             type: "application/pdf;charset-UTF-8"           });           const objectURL = URL.createObjectURL(file);           this.pdfList.push(objectURL);           this._loadFile(objectURL);           return;         }       } catch (error) {         console.log(error, "getPdfFile");       }     },     _loadFile(url) {       PDFJS.getDocument(url).promise.then(pdf => {         this.pdfDoc = pdf;         const pdfPages = this.pdfDoc.numPages;         this.pdfList = pdfPages;         this.$nextTick(() => {           this._renderPage(1);         });       });     },     _renderPage(num) {       this.pdfDoc.getPage(num).then(page => {         let canvas = document.getElementById("pdfCanvas" + num + this.index);         var vp = page.getViewport({ scale: 1 });         let ctx = canvas.getContext("2d");         let dpr = window.devicePixelRatio || 1;         let bsr =           ctx.webkitBackingStorePixelRatio ||           ctx.mozBackingStorePixelRatio ||           ctx.msBackingStorePixelRatio ||           ctx.oBackingStorePixelRatio ||           ctx.backingStorePixelRatio ||           1;         let ratio = dpr / bsr;         let viewport = page.getViewport({           scale: window.innerWidth / vp.width         });         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);         let renderContext = {           canvasContext: ctx,           viewport: viewport         };         console.log(canvas, "------");         if (canvas) {           page.render(renderContext);         }         if (this.pdfList > num) {           this._renderPage(num + 1);         }       });     } 复制代码

好家伙,本以为这样OK的,因为当时测试数据返回只有一条,渲染出来一点毛病都没有,非常的nice。然而吧,其实数组里面是可能存在多条的,所以此时高兴的太早了。
当数据出现了两条的时候,此时,问题来了,先看下控制台报错是什么

image.png 这,这,这,这怎么就报错了呢?然后页面渲染的pdf也是错乱的,直接旋转了180度,而且显示也不全了,Σ(っ °Д °;)っ
遇事不决先百度,发现,难道就没有人遇到过这个问题吗?还是说我这个需求比较奇葩?不行我再找找,然而,我失败了,我没找到。/(ㄒoㄒ)/~~
没办法,自己来吧。先翻译一波这是啥意思:

错误:在多个render()操作期间不能使用相同的画布。使用不同的画布或确保先前的操作已被取消或完成。 复制代码

操作期间不能使用相同的画布,emmmm,啥意思?我不是循环了吗?怎么会是同一个画布?
难道是高度问题?于是我设置了固定高度,失败。
难道是我循环嵌套的问题?于是我把canvas进行了封装,失败

<div v-if="pdfList.length > 0">       <div         v-for="(pdfPages, index) in pdfList"         :key="index"         class="pdf-preview"       >         <CanvasPreviewPdf :pdfData="pdfPages" :index="index" />       </div>     </div> 复制代码

等等,我似乎忽略了什么重要的信息,多个render操作,相同的画布,我的画布为什么会相同?这是个好问题。于是我仔细对比了一下渲染出来的canvas标签,好家伙,设置的id,渲染出来的时候是一样的,就是在父组件中循环的时候,里面的canvas,每一组的id都是一样的,如下图(这是我修改过后的,修改前,pdfCanvas10和pdfCanvas11都叫做pdfCanvas1,这也就意味着重复了),于是乎,我把父组件中循环的index拼接在了后面,问题解决,封装组件代码如下

image.png

<template>   <div>     <div v-for="page in pdfList" :key="page">       <canvas :id="'pdfCanvas' + page + index"></canvas>     </div>   </div> </template> <script> import PDFJS from "pdfjs-dist"; export default {   name: "PreviewPdf",   components: {},   data() {     return {       pdfList: []     };   },   props: {     pdfData: {       type: Object     },     index: {       type: Number     }   },   created() {     PDFJS.GlobalWorkerOptions.workerSrc = require("pdfjs-dist/build/pdf.worker.min.js");     this.getPdfFile(this.pdfData.licenseId);   },   methods: {     // pdf文件     async getPdfFile(licenseId) {       const url = ``;       try {         const res = await this.$http({           url,           method: "get",           params: {             licenseId           },           responseType: "blob"         });         if (res.status == 200) {           const content = res.data;           let file = new Blob([content], {             type: "application/pdf;charset-UTF-8"           });           const objectURL = URL.createObjectURL(file);           this.pdfList.push(objectURL);           this._loadFile(objectURL);           return;         }       } catch (error) {         console.log(error, "getPdfFile");       }     },     _loadFile(url) {       PDFJS.getDocument(url).promise.then(pdf => {         this.pdfDoc = pdf;         const pdfPages = this.pdfDoc.numPages;         this.pdfList = pdfPages;         this.$nextTick(() => {           this._renderPage(1);         });       });     },     _renderPage(num) {       this.pdfDoc.getPage(num).then(page => {         let canvas = document.getElementById("pdfCanvas" + num + this.index);         var vp = page.getViewport({ scale: 1 });         let ctx = canvas.getContext("2d");         let dpr = window.devicePixelRatio || 1;         let bsr =           ctx.webkitBackingStorePixelRatio ||           ctx.mozBackingStorePixelRatio ||           ctx.msBackingStorePixelRatio ||           ctx.oBackingStorePixelRatio ||           ctx.backingStorePixelRatio ||           1;         let ratio = dpr / bsr;         let viewport = page.getViewport({           scale: window.innerWidth / vp.width         });         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);         let renderContext = {           canvasContext: ctx,           viewport: viewport         };         console.log(canvas, "------");         if (canvas) {           page.render(renderContext);         }         if (this.pdfList > num) {           this._renderPage(num + 1);         }       });     }   } }; </script> <style scoped></style> 复制代码

后记

讲道理,真没想到是这么个问题。记录一下,或许能帮到和我遇到同样问题的伙伴


作者:路过的前端小菜鸟
链接:https://juejin.cn/post/7025960857427247111


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐