移动端 h5实现 pdf 预览 和下载

1,048 阅读5分钟

1. 实现背景

产品说要实现一个功能,移动端h5的 pdf的预览和下载。 好吧,那就预研一下开始实现吧。 知道这块之前有实现过,但是ios是预览效果, 安卓手机就直接下载了。 所以就直接废弃没有实现, 改为预览图片, 然后可以自行保存图片, 其实这样子实现的感觉也还可以的,主要转换给了后端。 这次要改为pdf的预览和下载,那么工作量就给到前端了。 在实现过程中,也是遇到了一些坑以及奇怪的问题, 这边进行记录总结一下。

 

2. 实现过程

pdf 预览:

2.1 pdfjs实现

首先看网上实现说是pdfjs实现效果很好,所以就按照步骤来进行实现了。 

贴一下pdfjs的地址: github.com/mozilla/pdf…

然后将pdfjs的内容进行clone下来, 然后将下面的web 与 build 文件夹 移入到自己项目的public文件夹下(可直接引用),ps: 这里好像也有坑,这里复制的文件夹是后端给我的,他写的时候好像说是某些时候也会预览不了,所以他改了源码。 说是他刚开始下载的是4.x版本, 后面改为3.x版本就可以的。

然后 当页面预览时,直接引入就可以了。

    <div class="pdf-preview-container" v-if="show">
      <iframe :src="curpdfUrl" class="pdf-iframe">
      </iframe>
    </div>

 

 this.curpdfUrl = '/PDFView/web/viewer.html?file=' + encodeURIComponent(this.pdfUrl.split(",")[i])

这里的pdf的路径 需要encodeURIComponent 转码一下。

这个的实现效果很好, 就是类似于pc端那种pdf预览, 

 

甚至可以自行在pdf上编辑标注等,我觉得是很完美了。

但是当把代码构建上去的时候, 测试反馈页面初始进去会很慢。 嗯? 开始进行反思, 其实我自己加载的时候也还好, 可能是是因为她开了停用缓存吧, 每次加载资源就是会这么慢的。 当我也停用缓存的时候,我发现是真的有点慢。 这时候开始有点不清楚了,因为确实引入了很多文件到项目中, 这两个文件夹的内容, 打包了一下看有370个文件好像。 我也确实不太喜欢这种方式, 把文件夹加入到自己项目中,会使项目变得越来越臃肿。 

在初始引入的时候, 就有想过引入包的形式, 好吧,看了一圈,pdfjs好像不支持包的引入,怎一个难用了得。

 

2.2 pdfjs-dist实现

pdfjs实现不好,那就换一种方式吧。 看pdfjs-dist实现的也很多, 主要的原理就是将pdf转换为canvas进行绘制。

在整理过程中 之前写的代码 也没完全找到了。没有我的提交记录。 那就大致写一下吧。

 <div>
        <canvas v-for="page in pages" :id="'the-canvas'+page" :key="page"></canvas>
      </div>



import PDFJS from 'pdfjs-dist';
import workerSrc from 'pdfjs-dist/build/pdf.worker.entry';


PDFJS.workerSrc = workerSrc;

   data() {
      return {
          pdfDoc: null,
          pages: 0,
          pdfUrl:'',
          src:'',
          loadding:true,
          file:true,
          isDestory:true
      }
    },



  _renderPage (num) {
      // getPage 处理每个页面
      // 返回单页内容实例(页面引索) pdf.getPage(index)
      this.pdfDoc.getPage(num).then((page) => {
        // canvas 绘制 PDF
        let canvas = document.getElementById('the-canvas' + num)
        let ctx = canvas.getContext('2d');
        ctx.mozImageSmoothingEnabled = false;
        ctx.webkitImageSmoothingEnabled = false;
        ctx.msImageSmoothingEnabled = false;
        ctx.imageSmoothingEnabled = false;
        let dpr = window.devicePixelRatio || 1
        let bsr = ctx.webkitBackingStorePixelRatio ||
                  ctx.mozBackingStorePixelRatio ||
                  ctx.msBackingStorePixelRatio ||
                  ctx.oBackingStorePixelRatio ||
                  ctx.backingStorePixelRatio || 1
        let ratio = dpr / bsr
        // 返回页面内容(比例) page.getViewport({scale:2.0})语法改这么写
        let viewport = page.getViewport({scale:screen.availWidth / page.getViewport({scale:1.0}).width});//这是让pdf文件的大小等于视口的大小
        canvas.width = viewport.width * ratio
        canvas.height = viewport.height * ratio//这里会进行压缩,解决模糊问题
        canvas.style.width = viewport.width + 'px'
        canvas.style.height = viewport.height + 'px'
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport,
          transform: [ratio, 0, 0, ratio, 0, 0]//这里会进行放大,解决模糊问题
        }
        const that = this
         page.render(renderContext).promise.then(function(){
          //  防止 在 PDF未渲染完某页 就返回时 会报错,导致下一次打开PDF 不会渲染出来
           if(that.isDestory){
             console.log('xuanran')
          // page.getTextContent()
            if (that.pages > num) {
            that._renderPage(num + 1)
          }
           }
        });
   },
    _loadFile (url) {
      // 获取整个 文档
      PDFJS.getDocument({
        url,
        // cMapUrl: 'https://unpkg.zhimg.com/pdfjs-dist@1.9.426/cmaps/',//这里同样要引入字体解决水印问题,需自己提供
        // 注意:如果PDF的水印是收费字体,则需要 通过cMapUrl引入对应的字体,如果是免费字体,则不需要引入字体 水印会直接渲染出来
        cMapPacked: true
      }).promise.then((pdf) => {
        this.pdfDoc = pdf
        this.pages = this.pdfDoc.numPages
        this.loadding = false
        const that = this
         if(that.isDestory){
            this.$nextTick(() => {
              this._renderPage(1)
            })
         }
      },(err) => {
        if(err.name == 'MissingPDFException'){
          this.$toast('无效的PDF链接')

        }
        // reject(err);
        })
    }
},
  mounted() {
  this._loadFile('http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf');
  },

 版本: "pdfjs-dist": "^2.5.207",  npm i pdfjs-dist@2.2.228 可自行进行安装。

但是 实现的效果一般,绘制出来的图片有点变形了。 也有可能是我参数没有调好。

2.3 pdfh5实现

继续查找, 这个pdfh5实现的使用也比较多,那也尝试一下吧。

贴一下地址:github.com/gjTool/pdfh…

    <div class="pdf-preview-container" v-if="show">
      <div id="demo"></div>
    </div>

import Pdfh5 from "pdfh5";

import "pdfh5/css/pdfh5.css";

    /**
     * pdf 预览
     */
    previewPdf(i) {  
      // this.curpdfUrl = '/PDFView/web/viewer.html?file=' + encodeURIComponent(this.pdfUrl.split(",")[i])
      this.curpdfUrl = this.pdfUrl.split(",")[i]
      this.show = true

      //实例化
      setTimeout(() => {
        this.pdfh5 = new Pdfh5("#demo", {
          pdfurl: this.curpdfUrl,
          // cMapUrl:"https://unpkg.com/pdfjs-dist@3.8.162/cmaps/",
          // responseType: "blob" // blob arraybuffer
        });
      })
      //监听完成事件
      // this.pdfh5.on("complete", function (status, msg, time) {
      // console.log("状态:" + status + ",信息:" + msg + ",耗时:" + time + "毫秒,总页数:")
      // //禁止手势缩放
      // this.pdfh5.zoomEnable(false);
      // })

    },

尝试了一下 使用效果还是不错的, 文档也没有变形,类似于文档的形式。 可以放大缩小,也有自带的回到顶部按钮。

给产品看了一下, 产品说 这种效果是可以的。很好,完成。

pdf 下载:

使用的下载方式, 是创建一个a链接的方式。 但是这种方式苹果手机可以正常下载,但是安卓的手机,就是提示要打开浏览器,而且打开了浏览器也没有正确的进行下载。

安卓基于微信系统下是没法直接下载的。

那就区分一下安卓与ios系统吧, 实现方式如下:

    downloadPdf(i) {
      const userAgent = navigator.userAgent;
      const isIOS = /iP(hone|od|ad)/.test(userAgent);
      // 安卓和ios 是走不同的下载方式。ios可直接下载, 安卓需从浏览器打开下载
      if (isIOS) {
        // ios 系统
        console.log("用户设备是iOS");
        fetch(this.pdfUrl.split(",")[i])
        .then(response => response.blob())
        .then(blob => {
          const url = window.URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.style.display = 'none';
          a.href = url;
          a.download = `报告${i+1}.pdf`;
          document.body.appendChild(a);
          a.click();
          window.URL.revokeObjectURL(url);
        })
        .catch(error => console.error('Error downloading the PDF:', error));
      } else {
        // 安卓系统
        console.log("用户设备不是iOS");
        window.open(this.pdfUrl.split(",")[i])
      }
    },

这时候,安卓手机就是跳转到外部浏览器进行下载, ios系统可自行下载或转发。

部分pdf打不开问题?

安卓手机反馈部分pdf会提示: 可在浏览器打开此网页下载文件。 但没有直接跳转浏览器。 原因是: 有的pdf上传的不规范, content-type = application/pdf, content-type有的不规范。这个就需要花时间调试了,就没管了。

3. 实现感想

其实整个过程,实现起来还是比较简单的。但是感觉自己也是需要不断的学习与进步, 在查找资料过程中,需要辨别与实现一下最优方式。 

学无止境, 继续加油呀~

还有就是 就算是困困的时候,居然还是写出来了,感觉和梦游一样。