前端预览pdf之pdfjs-dist结合better-scroll的使用

·  阅读 940

一、 纯pdfjs实现

  • 1、下载安装
npm i pdfjs-dist // 使用的版本是 "pdfjs-dist": "^2.14.305",
复制代码
  • 2、实现的功能
    • 1、在线 pdf 文件的预览
    • 2、支持按钮缩放
    • 3、支持手势缩放
<template>
  <div class="home_wrap">
    <div class="pdf_down">
      <div class="pdf_set_left" @click="scaleD">放大</div>
      <div class="pdf_set_middle" @click="scaleX">缩小</div>
    </div>

    <div
      :style="{ width: pdf_div_width, margin: '0 auto' }"
      @touchstart="touchstart"
      @touchmove="touchmove"
      @touchend="touchend"
    >
      <canvas
        v-for="page in pdf_pages"
        :id="'the_canvas' + page"
        :key="page"
      ></canvas>
    </div>
  </div>
</template>

<script>
let PDFJS = require('pdfjs-dist');
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.entry.js');
export default {
  props: {
    defaultSacleDelta: {
      type: Number,
      default: 1.1,
    },
    maxScale: {
      type: Number,
      default: 2,
    },
    minScale: {
      type: Number,
      default: 0.5,
    },
    defaultScale: {
      type: Number,
      default: 0.5,
    },
    pdfSrc: {
      type: String,
      default: 'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf'
    }
  },
  data() {
    return {
      currentScale: 0.5, //pdf放大系数
      pdf_pages: [],
      pdf_div_width: '',
      startX: 0,
      startY: 0,
      moveX: 0,
      moveY: 0,
      eLen: 0,
      touchDistance: null,
      startTime: null,
      previousPinchScale: 1,
      renderMode: false,
    };
  },
  created() {
    this.currentScale = this.defaultScale;
  },
  mounted() {
    this.get_pdfurl();
  },
  methods: {
    scaleD() {
      //放大
      if (this.currentScale >= this.maxScale) {
        return;
      }
      this.currentScale = this.currentScale + 0.1;
      this._loadFile(this.pdfSrc);
    },
    scaleX() {
      //缩小
      if (this.currentScale <= this.minScale) {
        return;
      }
      this.currentScale = this.currentScale - 0.1;
      this._loadFile(this.pdfSrc);
    },
    get_pdfurl() {
      //获得pdf教案
      //加载本地
      this._loadFile(this.pdfSrc);
      //线上请求
      //  this.$axios.get('')
      //  .then((res)=>{
      //  	this.pdfSrc = res.url
      //  	this._loadFile(this.pdfSrc)
      //  })
    },
    _loadFile(url) {
      console.log('_loadFile', this.currentScale);
      this.renderMode = false;
      let loadingTask = PDFJS.getDocument(url);
      loadingTask.promise.then((pdf) => {
        this.pdfDoc = pdf;
        this.pdf_pages = this.pdfDoc.numPages;
        this.$nextTick(() => {
          this._renderPage(1);
        });
      });
    },
    _renderPage(num) {
      const that = this;
      this.pdfDoc.getPage(num).then((page) => {
        let canvas = document.getElementById('the_canvas' + num);
        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: this.currentScale });
        canvas.width = viewport.width * ratio;
        canvas.height = viewport.height * ratio;
        canvas.style.width = viewport.width + 'px';
        that.pdf_div_width = viewport.width + 'px';
        canvas.style.height = viewport.height + 'px';
        ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport,
        };
        page.render(renderContext);
        if (this.pdf_pages > num) {
          this._renderPage(num + 1);
        } else {
          this.renderMode = true;
        }
      });
    },
    touchstart(e) {
      console.log('touchstart');
      this.startTime = this._getTime();
      this.startX = e.touches[0].pageX;
      this.startY = e.touches[0].pageY;
      if (e.touches.length > 1) {
        let point1 = e.touches[0];
        let ponit2 = e.touches[1];
        let xLen = Math.abs(ponit2.pageX - point1.pageX);
        let yLen = Math.abs(ponit2.pageY - point1.pageY);
        this.touchDistance = this._getDistance(xLen, yLen);
        console.log('touchDistance', this.touchDistance);
      }
    },
    touchmove(e) {
      console.log('touchmove');

      this.moveX = e.touches[0].pageX;
      this.moveY = e.touches[0].pageY;
      this.eLen = e.touches.length;
      if (this.eLen > 1) {
        let point1 = e.touches[0];
        let ponit2 = e.touches[1];
        let xLen = Math.abs(ponit2.pageX - point1.pageX);
        let yLen = Math.abs(ponit2.pageY - point1.pageY);
        let distance = this._getDistance(xLen, yLen);
        if (this.touchDistance) {
          let pinchScale = distance / this.touchDistance;
          this.previousPinchScale = pinchScale > 1 ? pinchScale : 1;
          console.log('previousPinchScale', this.previousPinchScale);
        }
      }
    },
    touchend() {
      console.log('touchend');
      let timestamp = this._getTime();
      if (
        (this.moveX !== null && Math.abs(this.moveX - this.startX > 0)) ||
        (this.moveY !== null && Math.abs(this.moveY - this.startY) > 0)
      ) {
        console.log('timeDis', timestamp - this.startTime);
        if (timestamp - this.startTime < 1000 && this.eLen > 1) {
          console.log('zoom-start');
          if (this.previousPinchScale > 1) {
            this.zoomIn();
          } else {
            this.zoomOut();
          }
        }
        this.eLen = 0;
      }
    },
    zoomIn() {
      console.log('zoomIn');
      console.log('renderMode', this.renderMode);
      if (!this.renderMode) return;
      let newScale = this.currentScale;
      if (newScale < this.maxScale) {
        newScale = newScale * this.defaultSacleDelta;
        newScale = Math.ceil(newScale * 10) / 10;
        if (this.currentScale === newScale) {
          newScale += 0.1;
        }
        newScale = Math.min(this.maxScale, newScale);
        this.currentScale = newScale;
        console.log(this.currentScale);
        this._loadFile(this.pdfSrc);
      }
    },
    zoomOut() {
      console.log('zoomOut');
      console.log('renderMode', this.renderMode);
      if (!this.renderMode) return;
      let newScale = this.currentScale;
      if (newScale > this.minScale) {
        newScale = newScale / this.defaultSacleDelta;
        newScale = Math.ceil(newScale * 10) / 10;
        if (this.currentScale === newScale) {
          newScale -= 0.1;
        }
        newScale = Math.max(this.minScale, newScale);
        this.currentScale = newScale;
        console.log(this.currentScale);
        this._loadFile(this.pdfSrc);
      }
    },
    _getDistance(xLen, yLen) {
      return Math.sqrt(xLen * xLen + yLen * yLen);
    },
    _getTime() {
      return new Date().getTime();
    },
  },
};
</script>
<style scoped>
.home_wrap {
  width: 100%;
  height: 100%;
}
.pdf_down {
  position: fixed;
  display: flex;
  z-index: 20;
  right: 26px;
  bottom: 7%;
}
.pdf_set_left {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
.pdf_set_middle {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
</style>

复制代码
  • 存在的问题 因为是touchend之后,重新渲染 pdf文件,所以手势缩放会有卡顿的感觉。解决办法引入better-scroll

二、pdfjs 结合 better-scroll

  • 1、package.json
"dependencies": {
    "@better-scroll/core": "^2.4.2",
    "@better-scroll/movable": "^2.4.2",
    "@better-scroll/zoom": "^2.4.2",
    "pdfjs-dist": "^2.14.305",
    "vue": "^3.2.13"
  },
复制代码
  • 2、代码
<template>
  <div class="home_wrap">
    <div class="pdf_down">
      <div class="pdf_set_left" @click="scaleD">放大</div>
      <div class="pdf_set_middle" @click="scaleX">缩小</div>
    </div>

    <div class="pdf-parent-container"  ref="scroll">
      <div
        id="pdf-container"
        :style="{ width: pdf_div_width, margin: '0 auto' }"
      >
        <canvas
          v-for="page in pdf_pages"
          :id="'the_canvas' + page"
          :key="page"
        ></canvas>
      </div>
    </div>
  </div>
</template>

<script>
import BScroll from '@better-scroll/core';
import Movable from '@better-scroll/movable';
import Zoom from '@better-scroll/zoom';
let PDFJS = require('pdfjs-dist');
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.entry.js');
BScroll.use(Movable);
BScroll.use(Zoom);
export default {
  props: {
    defaultSacleDelta: {
      type: Number,
      default: 1.1,
    },
    maxScale: {
      type: Number,
      default: 2,
    },
    minScale: {
      type: Number,
      default: 0.5,
    },
    defaultScale: {
      type: Number,
      default: 1,
    },
    pdfSrc: {
      type: String,
      default:
        'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf',
    },
  },
  data() {
    return {
      currentScale: 0.5, //pdf放大系数
      pdf_pages: [],
      pdf_div_width: '',
      startX: 0,
      startY: 0,
      moveX: 0,
      moveY: 0,
      eLen: 0,
      touchDistance: null,
      startTime: null,
      previousPinchScale: 1,
      renderMode: false,
    };
  },
  created() {
    this.currentScale = this.defaultScale;
  },
  mounted() {
    this.get_pdfurl();
  },
  methods: {
    scaleD() {
      //放大
      if (this.currentScale >= this.maxScale) {
        return;
      }
      this.currentScale = this.currentScale + 0.1;
      this._loadFile(this.pdfSrc);
    },
    scaleX() {
      //缩小
      if (this.currentScale <= this.minScale) {
        return;
      }
      this.currentScale = this.currentScale - 0.1;
      this._loadFile(this.pdfSrc);
    },
    get_pdfurl() {
      //获得pdf教案
      //加载本地
      this._loadFile(this.pdfSrc);
      //线上请求
      //  this.$axios.get('')
      //  .then((res)=>{
      //  	this.pdfSrc = res.url
      //  	this._loadFile(this.pdfSrc)
      //  })
    },
    _loadFile(url) {
      console.log('_loadFile', this.currentScale);
      this.renderMode = false;
      let loadingTask = PDFJS.getDocument(url);
      loadingTask.promise.then((pdf) => {
        this.pdfDoc = pdf;
        this.pdf_pages = this.pdfDoc.numPages;
        this.$nextTick(() => {
          this._renderPage(1);
        });
      });
    },
    _renderPage(num) {
      const that = this;
      this.pdfDoc.getPage(num).then((page) => {
        let canvas = document.getElementById('the_canvas' + num);
        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: this.currentScale });
        canvas.width = viewport.width * ratio;
        canvas.height = viewport.height * ratio;
        canvas.style.width = viewport.width + 'px';
        that.pdf_div_width = viewport.width + 'px';
        canvas.style.height = viewport.height + 'px';
        ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
        let renderContext = {
          canvasContext: ctx,
          viewport: viewport,
        };
        page.render(renderContext);
        if (this.pdf_pages > num) {
          this._renderPage(num + 1);
        } else {
          this.renderMode = true;
          this.bs = new BScroll(this.$refs.scroll, {
            bindToTarget: true,
            scrollX: true,
            scrollY: true,
            freeScroll: true,
            movable: true,
            zoom: {
              start: 1,
              min: 0.5,
              max: 5,
            },
          });
        }
      });
    },
  },
};
</script>
<style scoped>
.home_wrap {
  width: 100%;
  height: 100%;
}
.pdf_down {
  position: fixed;
  display: flex;
  z-index: 20;
  right: 26px;
  bottom: 7%;
}
.pdf_set_left {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
.pdf_set_middle {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
.pdf-parent-container {
  width: 100%;
  height: 80vh;
  border: 2px solid red;
  overflow: scroll;
}
</style>

复制代码

三、vite 构建的Vue项目中使用 pdfjs-dist

  • 1、package.json
 "dependencies": {
   "pdfjs-dist": "^2.14.305",
   "vue": "^3.2.25"
 },
复制代码
  • 2、代码
<template>
 <div>
   pdf-dist {{ pdfPages }}
   <canvas
     v-for="page in pdfPages"
     :id="'the_canvas' + page"
     :key="page"
   ></canvas>
 </div>
</template>
<script>
import { nextTick, onMounted, ref } from 'vue';

export default {
 setup() {
   let pdfPages = ref(0);
   let pdfDoc = null; // 不要使用 ref 或 reactive 响应式数据定义,因为下面要使用该对象中的私有方法(getPage).
   onMounted(async () => {
     let PDFJS = await import('pdfjs-dist');
     PDFJS.GlobalWorkerOptions.workerSrc =
       'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.14.305/pdf.worker.min.js'; // 注意版本号,一定要和 package.json 中 pdfjs-dist 的版本要一致
     renderFn(PDFJS);
   });
   const renderFn = (PDFJS) => {
     const url =
       'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf';
     let loadingTask = PDFJS.getDocument(url);
     console.log(1);
     loadingTask.promise.then((pdf) => {
       pdfDoc = pdf;
       pdfPages.value = pdf.numPages;
       nextTick(() => {
         console.log('nextTick');
         renderPage(1)
       });
     });
     const renderPage = (num) => {
       console.log('renderPage')
        pdfDoc.getPage(num).then((page) => {
         let canvas = document.getElementById('the_canvas' + num);
         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: 1 });
         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,
         };
         page.render(renderContext);
         if(num < pdfPages.value) {
           renderPage(num + 1)
         }
       });
     };
   };
   return {
     pdfPages,
   };
 },
};
</script>
<style>
#app {
 font-family: Avenir, Helvetica, Arial, sans-serif;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
 text-align: center;
 color: #2c3e50;
 margin-top: 60px;
}
</style>

复制代码

四、解决pdf放大后字体模糊 & IOS 端 canvas绘制空白

解决思路

  • 1、字体模糊:初始化渲染的scale 大于 1,然后用 better-scroll 缩放到小尺寸
  • 2、ISO端渲染scale 大于1时, canvas显示空白:使用svg绘制
<template>
  <div class="home_wrap">
    <div class="pdf_down">
      <div class="pdf_set_left" @click="scaleD">放大</div>
      <div class="pdf_set_middle" @click="scaleX">缩小</div>
      <div class="pdf_set_middle" @click="onRoate">旋转</div>
    </div>

    <div class="pdf-parent-container" ref="scroll">
      <div
        id="pdf-container"
        :style="{ width: pdf_div_width, margin: '0 auto' }"
      >
        <div
          v-for="page in pdf_pages"
          class="pdf-page-item"
          :id="'the_page' + page"
          :key="page"
        ></div>
      </div>
    </div>
  </div>
</template>

<script>
import BScroll from '@better-scroll/core';
import Movable from '@better-scroll/movable';
import Zoom from '@better-scroll/zoom';
let PDFJS = require('pdfjs-dist');
PDFJS.GlobalWorkerOptions.workerSrc = require('pdfjs-dist/build/pdf.worker.entry.js');
BScroll.use(Movable);
BScroll.use(Zoom);
export default {
  props: {
    defaultSacleDelta: {
      type: Number,
      default: 1.1,
    },
    maxScale: {
      type: Number,
      default: 2,
    },
    minScale: {
      type: Number,
      default: 0.5,
    },
    defaultScale: {
      type: Number,
      default: 3,
    },
    pdfSrc: {
      type: String,
      default:
        'http://storage.xuetangx.com/public_assets/xuetangx/PDF/PlayerAPI_v1.0.6.pdf',
    },
  },
  data() {
    return {
      currentScale: 5, //pdf放大系数
      pdf_pages: [],
      pdf_div_width: '',
      startX: 0,
      startY: 0,
      moveX: 0,
      moveY: 0,
      eLen: 0,
      touchDistance: null,
      startTime: null,
      previousPinchScale: 1,
      renderMode: false,
      rotateAngle: 0,
    };
  },
  created() {
    this.currentScale = 5;
  },
  mounted() {
    this.get_pdfurl();
  },
  methods: {
    scaleD() {
      //放大
      if (this.currentScale >= this.maxScale) {
        return;
      }
      this.currentScale = this.currentScale + 0.1;
      this._loadFile(this.pdfSrc);
    },
    scaleX() {
      //缩小
      if (this.currentScale <= this.minScale) {
        return;
      }
      this.currentScale = this.currentScale - 0.1;
      this._loadFile(this.pdfSrc);
    },
    onRoate() {
      this.rotateAngle += 90;
      if (this.rotateAngle >= 360) {
        this.rotateAngle = 0;
      }
      let pages = document.querySelectorAll('.pdf-page-item');

      pages.forEach((el) => {
        el.style.transform = `rotate(${this.rotateAngle}deg)`;
      });
    },
    get_pdfurl() {
      //获得pdf教案
      //加载本地
      this._loadFile(this.pdfSrc);
      //线上请求
      //  this.$axios.get('')
      //  .then((res)=>{
      //  	this.pdfSrc = res.url
      //  	this._loadFile(this.pdfSrc)
      //  })
    },
    _loadFile(url) {
      console.log('_loadFile', this.currentScale);
      this.renderMode = false;
      let loadingTask = PDFJS.getDocument(url);
      loadingTask.promise.then((pdf) => {
        this.pdfDoc = pdf;
        this.pdf_pages = this.pdfDoc.numPages;
        this.$nextTick(() => {
          this._renderPage(1);
        });
      });
    },
    _renderPage(num) {
      const that = this;
      this.pdfDoc.getPage(num).then((page) => {
        let pageContainer = document.getElementById('the_page' + num);
        let viewport = page.getViewport({ scale: this.currentScale });

        pageContainer.style.width = viewport.width + 'px';
        that.pdf_div_width = viewport.width + 'px';
        pageContainer.style.height = viewport.height + 'px';

        page.getOperatorList().then((opList) => {
          var svgFx = new PDFJS.SVGGraphics(page.commonObjs, page.objs);
          return svgFx.getSVG(opList, viewport).then(function (svg) {
            pageContainer.appendChild(svg);
          });
        });
        if (this.pdf_pages > num) {
          this._renderPage(num + 1);
        } else {
          this.renderMode = true;
          this.$nextTick(() => {
            this.bs = new BScroll(this.$refs.scroll, {
              bindToTarget: true,
              scrollX: true,
              scrollY: true,
              freeScroll: true,
              movable: true,
              zoom: {
                start: 0.2,
                min: 0.1,
                max: 5,
              },
            });
          });
          // this.bs.zoomTo(0.1, 'center', 'center');

          let flag = true;
          this.bs.on('zoomEnd', ({ scale }) => {
            // use scale
            if (!flag) {
              return;
            }
            this.bs.zoomTo(1, 100, 100);
            flag = !flag;

            console.log(scale); // 当前 scale 的值

            this.currentScale = scale;
            this._renderPage(1);
          });
        }
      });
    },
  },
};
</script>
<style scoped>
.home_wrap {
  width: 100%;
  height: 100%;
}
.pdf_down {
  position: fixed;
  display: flex;
  z-index: 20;
  right: 26px;
  bottom: 7%;
}
.pdf_set_left {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
.pdf_set_middle {
  width: 30px;
  height: 40px;
  color: #408fff;
  font-size: 11px;
  padding-top: 25px;
  text-align: center;
  margin-right: 5px;
  cursor: pointer;
}
.pdf-parent-container {
  width: 100%;
  height: 80vh;
  border: 2px solid red;
  overflow: hidden;
}
</style>

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