pdfjs-dist vue2 elementUI2 实现PDF预览 兼容IE11

606 阅读1分钟

为了兼容IE11,选型了pdfjs-dist;本来用了vue-pdf,但是IE11上面会出现文字丢失情况,只能换方案。

实现了 上下翻页、放大、缩小、输入页码跳转页面、左侧缩略图等功能

pdf文件来自后端接口BLOB

实现效果

<template>
  <div>
    <div class="toolbar">
      <div>
        <span
          class="backBtn"
          @click="goBack"
        ><i class="el-icon-arrow-left" />返回</span>
      </div>
      <div style="padding-left: 200px">
        <el-button
          type="text"
          icon="el-icon-arrow-left"
          size="mini"
          @click="prePage"
        >
          上一页
        </el-button>
        <span class="pageNum">
          <el-input v-model="currentPage" /> / {{ pageTotalNum }}
        </span>
        <el-button
          type="text"
          size="mini"
          @click="nextPage"
        >
          下一页<i class="el-icon-arrow-right el-icon--right" />
        </el-button>
        <el-button
          type="text"
          size="mini"
          @click="scaleD"
        >
          放大
        </el-button>
        <el-button
          type="text"
          size="mini"
          @click="scaleX"
        >
          缩小
        </el-button>
      </div>
      <div />
    </div>
    <div class="container">
      <el-scrollbar class="scrollBarWrapper">
        <div
          id="thumbnailContainer"
          ref="scrollThumWrapper"
        />
      </el-scrollbar>
      <div class="pdfContainerWrapper">
        <div id="pdfContainer" />
      </div>
    </div>
  </div>
</template>
<style>
.scrollBarWrapper {
  height: 1305px;
}
.scrollBarWrapper .el-scrollbar__wrap {
  overflow-x: hidden;
  overflow-y: scroll;
}
.toolbar .el-input {
  width: 32px;
}
.toolbar .el-input .el-input__inner {
  background-color: #555555;
  color: #ffffff;
  width: 32px;
  padding: 0;
  height: 20px;
  text-align: center;
  line-height: 18px;
}
#thumbnailContainer > .thumItem {
  margin: 10px 0;
  cursor: pointer;
}
.thumItem.active {
  border: 5px solid #87cef0;
}
</style>

<style scoped>
.toolbar {
  font-size: 14px;
  height: 46px;
  width: 100%;
  background-color: #444444;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 50px 0 10px;
  border-bottom: 1px solid #888888;
}
.backBtn {
  color: #409eff;
  cursor: pointer;
}
.pageNum {
  height: 29px;
  line-height: 29px;
  color: #409eff;
  margin: 0 20px;
}
.container {
  display: flex;
  background-color: #555555;
  position: relative;
}
#thumbnailContainer {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 260px;
  background-color: #666666;
}
.pdfContainerWrapper {
  width: calc(100% - 260px);
  padding: 20px;
  display: flex;
  justify-content: center;
}
</style>
<script>
import PDF from 'pdfjs-dist';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
PDF.GlobalWorkerOptions.workerSrc = pdfjsWorker;

export default {
  name: 'IEPreview',
  data() {
    return {
      pdfInstance: null, // pdf实例
      pageTotalNum: 0, // 拿到的pdf总页数
      scale: 1.5,
      maxScale: 2.5,
      minScale: 0.9,
      scaleStep: 0.2,
      thumScale: 0.35,
      currentPage: 1, // 拿到的pdf当前页数,
      prevPage: 1, // 记录上一页;兼容输入框跳页情况
      thumItemActive: 'thumItem active',
      pdfUrld: '' // pdf文件路径变量
    };
  },
  watch: {
    currentPage(newVal) {
      const num = Number(newVal);
      if (typeof num === 'number' && !Number.isNaN(num) && num > 0 && num <= this.currentPage) {
        this.goToPage(num);
      }
    }
  },
  beforeMount() {
    this.loading = this.$loading({
      lock: true,
      target: '.pdfViewerContent',
      text: '加载中',
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.7)'
    });
  },
  mounted() {
    const folder = /folder=(.*)&/.exec(location.hash)[1]?.trim();
    const fileName = /fileName=(.*)/.exec(location.hash)[1]?.trim();
    this.fileParent = folder;
    const url = `${process.env.VUE_APP_BASE_API}/policy/file/info?folder=${folder}&filename=${fileName}&ts=${(new Date()).valueOf()}`;
    this.init(url);
  },
  methods: {
    // 翻页同时缩略图滚动
    thumbnailScroll() {
      if (this.currentPage === 1) {
        return false;
      }
      this.$refs.scrollThumWrapper.parentNode.parentNode.scrollTop = (this.currentPage > 9 ? this.currentPage : this.currentPage - 1) * 300;
    },

    // 跳页
    goToPage(page) {
      this.pdfContainer.children[this.prevPage - 1].style.display = 'none';
      this.thumbnailContainer.children[(this.prevPage - 1) * 2].className = 'thumItem';
      this.currentPage = page;
      this.prevPage = page;
      this.pdfContainer.children[page - 1].style.display = 'inline-block';
      this.thumbnailContainer.children[(page - 1) * 2].className = this.thumItemActive;
      this.thumbnailScroll();
    },

    // 回到列表页
    goBack() {
      if (['company', 'it', 'form'].includes(this.fileParent)) {
        this.$router.push(`/policy/${this.fileParent}`);
        return;
      }
    },
    // 上一页
    prePage() {
      let page = this.currentPage;
      page = page > 1 ? page - 1 : this.pageTotalNum;
      this.goToPage(page);
    },

    // 下一页
    nextPage() {
      let page = this.currentPage;
      page = page < this.pageTotalNum ? page + 1 : 1;
      this.goToPage(page);
    },

    /**
     * 放大或者缩小之后,重新渲染
     */
    afterChangeScale() {
      const pdfContainer = this.pdfContainer;
      while (pdfContainer.firstChild) {
        pdfContainer.removeChild(pdfContainer.firstChild);
      }
      for (let pageNum = 1; pageNum <= this.pageTotalNum; pageNum++) {
        this.pdfInstance.getPage(pageNum).then((page) => {
          this.createCanvas({
            page,
            container: pdfContainer,
            isThum: false,
            pageNum
          });
          if (pageNum === this.pageTotalNum) {
            pdfContainer.children[0].style.display = 'none';
            pdfContainer.children[this.currentPage - 1].style.display = 'inline-block';
          }
        });
      }
    },

    // 放大
    scaleD() {
      this.scale += this.scaleStep;
      if (this.scale < this.maxScale) {
        this.afterChangeScale();
      } else {
        this.$message.info('已放大到最大比例');
        this.scale -= this.scaleStep;
      }
    },

    // 缩小
    scaleX() {
      this.scale -= this.scaleStep;
      if (this.scale > this.minScale) {
        this.afterChangeScale();
      } else {
        this.$message.info('已缩小到最小比例');
        this.scale += this.scaleStep;
      }
    },
    renderPDF({ page, canvas, viewport }) {
      const context = canvas.getContext('2d');

      const renderContext = {
        canvasContext: context,
        viewport: viewport
      };
      page.render(renderContext);
    },
    // 为每一张PDF页面创建canvas,并插入到缩略图区域和PDF预览区域
    createCanvas({ page, container, pageNum, vw, isThum }) {
      const viewport = vw || page.getViewport(this.scale);
      const canvasEl = document.createElement('canvas');
      const pageNumEl = document.createElement('div');
      if (pageNum !== 1) {
        canvasEl.style.display = 'none';
      }
      if (isThum) {
        canvasEl.className = this.currentPage === pageNum ? this.thumItemActive : 'thumItem';
        canvasEl.onclick = () => { this.goToPage(pageNum); };
        canvasEl.style.display = 'inline-block';
        pageNumEl.innerText = pageNum;
        pageNumEl.style.color = '#ffffff';
      }
      container.appendChild(canvasEl);
      if (isThum) {
        container.appendChild(pageNumEl);
      }
      canvasEl.height = viewport.height;
      canvasEl.width = viewport.width;
      this.renderPDF({
        page,
        canvas: canvasEl,
        viewport
      });
      this.loading.close();
    },
    // 处理拿到的pdf文件实例
    handlePDF(total) {
      // 对文件的每一页进行渲染
      for (let pageNum = 1; pageNum <= total; pageNum++) {
        this.pdfInstance.getPage(pageNum).then((page) => {
          this.pdfContainer = document.getElementById('pdfContainer');
          this.thumbnailContainer = document.getElementById('thumbnailContainer');
          // 渲染PDF预览区域
          this.createCanvas({
            page,
            container: this.pdfContainer,
            isThum: false,
            pageNum
          });
          // 渲染缩略图
          this.createCanvas({
            page,
            container: this.thumbnailContainer,
            pageNum,
            isThum: true,
            vw: page.getViewport(this.thumScale)
          });
        });
      }
    },
    // 初始化加载blob格式PDF文件
    init(url) {
      const padUrlsd = url || this.pdfUrld;
      const handlePDF = this.handlePDF;
      PDF.getDocument(padUrlsd).then((pdf) => {
        const total = this.pageTotalNum = pdf._pdfInfo.numPages;
        this.pdfInstance = pdf;
        handlePDF(total);
      }).catch((error) => {
        console.log(error);
        this.$message.error('获取pdf文件失败');
        this.loading.close();
      });
    }
  }
};
</script>