背景

产品需求涉及到动态的PDF展示,PDF是由后端去生成的,然后下发给前端在线的cdn地址,H5需要实现在线PDF预览的能力

方案

H5展示合同PDF,有很多实现方式。但是通过尝试后发现在不同操作系统会存在兼容性问题

方案表现
iframe的形式iOS:只能展示第一页,多页不能展示
Android: 弹出下载弹窗
PC:正常展示
embed标签iOS:只能展示第一页
Android: 弹出下载弹窗
PC:显示不出来
vue-pdf(基于pdfjs封装各端都能正常展示,且可以根据需要展示PDF的全部页数,设定翻页操作,但是部署到生产环境时存在bug,且该库已经一年多没有维护了
pdf.js各端均能正常展示

最终选择了使用pdf.js,日常开发中如果原生标签能满足的还是尽量用简单的方案处理,但是此次需求涉及到较为复杂的PDF操作,所以对比之下还是使用了插件的形式

解决方案
 <canvas
          v-for="page in pdfPages"
          :key="page"
          :id="'pdf-canvas' + page"
        />
<script>
  //以es5形式引入,解决低端浏览器兼容性问题
  const PDFJS = require("pdfjs-dist/es5/build/pdf");
  //pdfjs-dist的版本也是2.5.207
  PDFJS.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.5.207/pdf.worker.js";

  export default {
    name: "Contract",
    data: function () {
      return {
        pdfPages: [], //页数
        pdfWidth: "", // 宽度
        pdfSrc: '', //地址
        pdfDoc: "", //文档内容
        pdfScale: 1, //放大倍数
        contractUrl: '', //后端返回的PDF链接
      }
    },
    mounted() {
      this.getData()
    },
    methods: {
      getData() {
        getContractUrl().then(res => {
          if (res.data) {
            let {contract_url, email} = res.data
            this.contractUrl = contract_url
            this.pdfSrc = contract_url
            if(contract_url){
              this.loadFile(this.pdfSrc)
            }
          }
        }).catch(err => {
          console.log('getContractUrl err', err)
        })
      },
      loadFile(url) {
        this.$Loading.show()
        let loadingTask = PDFJS.getDocument(url);

        loadingTask.promise.then(pdf => {
          this.$Loading.hide()
          this.pdfDoc = pdf;
          this.pdfPages = pdf.numPages;
          this.$nextTick(() => {
            this.renderPage(1);
          });
        }).catch(err=>{
          console.log(err, 'loadingTask err')
        });
      },
      renderPage(num) {
        const that = this;
        this.pdfDoc.getPage(num).then(page => {
          let canvas = document.getElementById("pdf-canvas" + num);
          let ctx = canvas.getContext("2d");
          let dpr = window.devicePixelRatio || 1;
          let ratio = dpr;
          // screen.availWidth 屏幕可用宽度
          let viewport = page.getViewport({scale: screen.availWidth / page.getViewport({scale: this.pdfScale}).width})

          // console.log(viewport,'-----viewport')

          canvas.width = viewport.width * ratio;
          canvas.height = viewport.height * ratio;

          canvas.style.width = viewport.width + "px";
          canvas.style.height = viewport.height + 'px'

          that.pdfWidth = viewport.width + "px";

          canvas.style.height = viewport.height + "px";

          ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
          // 将 PDF 页面渲染到 canvas 上下文中
          let renderContext = {
            canvasContext: ctx,
            viewport: viewport
          };
          page.render(renderContext);
          if (this.pdfPages > num) {
            this.renderPage(num + 1);
          }
        });
      }
   }
</script>
踩坑点
1、移动端适配问题

需要设置好viewport,要不然canvas渲染无法做到屏幕适配

let dpr = window.devicePixelRatio || 1; //获取设备像素比
let ratio = dpr
         
// screen.availWidth 屏幕可用宽度
let viewport = page.getViewport({scale: screen.availWidth / page.getViewport({scale: this.pdfScale}).width})
2、低端浏览器渲染报错environment lacks native support…

需要修改下引用pdfjs-dist的方式
之前是

  const PDFJS = require("pdfjs-dist");

需要改成

  const PDFJS = require("pdfjs-dist/es5/build/pdf");
参考

vue项目,npm install方式使用pdfjs

Vue 集成 PDF.js 实现 PDF 预览和添加水印

Reading pdf from url with node.js using PDF.js

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐