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

·  阅读 2037
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>
复制代码

后记

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

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改