zoukankan      html  css  js  c++  java
  • pdf.js 前端pdf预览 渲染文本图层支持复制 保证手机端清晰度 双指缩放 alloyfinger(手势)

    预览地址: https://feiyefeihua.gitee.io/

    一、渲染pdf核心代码

    首先安装 pdf.js 的 npm 版本:

    npm i pdfjs-dist@2.6.347

    使用:

    import * as PDFJS from "pdfjs-dist"
     
    其中,pdf.js 加载pdf文件依赖了 pdf.worker.js ,通过指定 pdf.worker.js 路径让 pdf.js 加载依赖:
      
    PDFJS.GlobalWorkerOptions.workerSrc ="https://cdn.jsdelivr.net/npm/pdfjs-dist@2.6.347/build/pdf.worker.min.js";
    这里使用了一个 CDN 地址,自己选择。也可以引用安装的 npm 包里面的 pdf.worker.js :
    window.pdfjsWorker = require("pdfjs-dist/build/pdf.worker.js")
     
     
    有两点需要注意:
    1. npm 库的使用没有使用最新版。因为最新版的使用了一些JS的新语法,比如  ?. 可选链:
    let testObj = {
      name: '可选链',
      tips: '防止访问不存在的属性而报错'
    }
    
    console.log(testObj.names?.nickname) // undefined
    console.log(testObj.names.nickname) // Uncaught TypeError: Cannot read property 'nickname' of undefined
    

    这会导致某些不支持的浏览器显示空白页面。我使用最新版的 pdfjs-dist , vue 打包的demo页面,在  QQ、UC 微信内置浏览器打开都是空白的。

    所以使用了 2.6.347 版本,再新的都有用新语法。

    2. pdf.worker.js 依赖问题。pdf.js 不是非要用 npm 形式使用, 直接在页面使用 script 标签引用也可以,并且可以只引用 pdf.js 即可(保证 pdf.worker.js 的名字,并且和 pdf.s 同路径)。

    pdf.js 源码里有做判断,有 pdf.worker.js 的地址就是使用;没有,就判断全局是否有 pdfjsWorker 对象;再没有,就通过 引用 pdf.js 的地址拼接出来  pdf.worker.js 的地址。

    使用:

    const canvasBoxEl = document.getElementById('canvas-box')
    this.boxWidth = canvasBoxEl.width
    this.boxHeight = canvasBoxEl.height
    console.log(canvasBoxEl)
    var url = './static/react-native.pdf'
    PDFJS.getDocument(url)
      .promise.then(pdf => {
        // pdf.numPages 总页数
        return pdf.getPage(1)
      })
      .then(page => {
        // 设置展示比例
        var scale = 1.5
        // 获取pdf尺寸
        var viewport = page.getViewport({ scale })
        console.log(viewport)
        // 获取需要渲染的元素
        var canvas = document.createElement('canvas')
        var context = canvas.getContext('2d')
        canvas.height = viewport.height
        canvas.width = viewport.width
        canvasBoxEl.appendChild(canvas)
        var renderContext = {
          canvasContext: context,
          viewport: viewport
        }
    
        page.render(renderContext)
      })

     如果有多页,看需求渲染。在 PC 这样差不多就可以了,但是在手上打开,可能就很模糊。

    PS:比较乱,基本功能都实现了,还需要优化。

    二、保证pdf清晰度

    pdf.js 是 mozilla 的开源库(地址:https://mozilla.github.io/pdf.js/),并且有一个完整的web展示页面,在手机上看也清晰。就从它的源码里面去看。

    其中渲染pdf有三个地方,一个是渲染的缩略图,一个是打印的样式,剩下的那个才是正文:

    主要是在渲染之前做了一些计算,canvas的 标签属性 width、height 和 css属性 width、height。还可以用svg渲染的样子?

    计算使用的是一些提前确认的变量和写好的工具函数,提取出来:

    const CSS_UNITS = 96.0 / 72.0;
    // const PRINT_UNITS = 150 / 72.0;
    
    let userAgent = (typeof navigator !== "undefined" && navigator.userAgent) || "";
    let platform = (typeof navigator !== "undefined" && navigator.platform) || "";
    let maxTouchPoints =
      (typeof navigator !== "undefined" && navigator.maxTouchPoints) || 1;
    
    let maxCanvasPixels = 16777216;
    
    let isAndroid = /Android/.test(userAgent);
    let isIOS =
      /(iPad|iPhone|iPod)(?=;)/.test(userAgent) ||
      (platform === "MacIntel" && maxTouchPoints > 1);
    (function checkCanvasSizeLimitation() {
      if (isIOS || isAndroid) {
        maxCanvasPixels = 5242880;
      }
    })();
    
    function approximateFraction(x) {
      if (Math.floor(x) === x) {
        return [x, 1];
      }
    
      var xinv = 1 / x;
      var limit = 8;
    
      if (xinv > limit) {
        return [1, limit];
      } else if (Math.floor(xinv) === xinv) {
        return [1, xinv];
      }
    
      var x_ = x > 1 ? xinv : x;
      var a = 0,
        b = 1,
        c = 1,
        d = 1;
    
      while (q < limit) {
        var p = a + c,
          q = b + d;
    
        if (q > limit) {
          break;
        }
    
        if (x_ <= p / q) {
          c = p;
          d = q;
        } else {
          a = p;
          b = q;
        }
      }
    
      var result;
    
      if (x_ - a / b < c / d - x_) {
        result = x_ === x ? [a, b] : [b, a];
      } else {
        result = x_ === x ? [c, d] : [d, c];
      }
    
      return result;
    },
    function roundToDivide(x, div) {
      var r = x % div;
      return r === 0 ? x : Math.round(x - r + div);
    }

    其中还有个根据 canvas 的 ctx 计算 pixelRatio 的:

    let devicePixelRatio = window.devicePixelRatio || 1
          let backingStoreRatio =
            ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio ||
            1
     const pixelRatio = devicePixelRatio / backingStoreRatio

    修改过后的完整代码:

    <template>
      <div class="pdf-touch-box">
        <div class="scale-btn-box" :style="{  btnWidth + 'px' }">
          <button class="scale-btn" @click="scaleCanvas(1.5)">1.5</button>
          <button class="scale-btn" @click="scaleCanvas(0.5)">0.5</button>
          <button class="scale-btn" @click="scaleCanvas(1)">还原(1)</button>
        </div>
    
        <div v-show="!loading" class="pdf-canvas-wrap" :style="{  viewWidth + 'px', height: viewHeight + 'px' }"></div>
        <p class="pdf-canvas-tips" v-show="loading">正在加载...</p>
      </div>
    </template>
    
    <script>
    import * as PDFJS from 'pdfjs-dist'
    
    // 本地
    // window.pdfjsWorker = require("pdfjs-dist/build/pdf.worker.js");
    
    // cdn 2.8.335  2.6.347 2.5.207
    PDFJS.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.6.347/build/pdf.worker.min.js'
    
    // https://github.com/mozilla/pdf.js/blob/master/examples/node/getinfo.js
    // Requires single file built version of PDF.js -- please run
    // `gulp singlefile` before running the example.
    // const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.js");
    
    const CSS_UNITS = 96.0 / 72.0
    // const PRINT_UNITS = 150 / 72.0;
    
    let userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || ''
    let platform = (typeof navigator !== 'undefined' && navigator.platform) || ''
    let maxTouchPoints = (typeof navigator !== 'undefined' && navigator.maxTouchPoints) || 1
    
    let maxCanvasPixels = 16777216
    // PDF之外占据的宽度 -18 padding -18减去滚动条宽度(不确定)
    let autoWidth = 36
    
    let isAndroid = /Android/.test(userAgent)
    let isIOS = /(iPad|iPhone|iPod)(?=;)/.test(userAgent) || (platform === 'MacIntel' && maxTouchPoints > 1)
    ;(function checkCanvasSizeLimitation() {
      if (isIOS || isAndroid) {
        maxCanvasPixels = 5242880
        autoWidth -= 18
      }
    })()
    
    export default {
      data() {
        return {
          src: './static/react-native.pdf',
          loading: true,
          pdfDoc: null,
          boxEl: null,
          wrapEl: null,
          areaWidth: 0,
          btnWidth: 0,
          viewWidth: 0,
          viewHeight: 0,
          pixelRatio: 2,
          isFirstTimeRender: true,
          viewport: null,
          canvasEles: [],
          canvasCtxs: [],
          totallPage: 1,
          pageScale: 1, // pdf 适应窗口产生的 scale
          curCanvasCSSWh: null,
          transform: null,
          pageRendered: false
        }
      },
      mounted() {
        this.init()
      },
      methods: {
        async init() {
          this.boxEl = document.querySelector('.pdf-touch-box')
          this.wrapEl = document.getElementsByClassName('pdf-canvas-wrap')[0]
          this.btnWidth = this.areaWidth = this.boxEl.clientWidth
    
          const loadingState = await this.getPDF()
          if (loadingState === 'success') this.initRenderOneByOne()
          else this.boxEl.innerText = loadingState
        },
        scaleCanvas(scale) {
          if (!this.pageRendered) return
    
          // 改变 viewport 大小
          this.viewport = this.viewport.clone({
            scale: this.pageScale * scale * CSS_UNITS
          })
          const { styleWidth, styleHeight } = this.getCanvasCSSWH()
          // 先改变CSS canvas 会变模糊
          this.canvasEles.forEach((canvas, index) => {
            // 不修改 width height 不然会重置 canvas
            canvas.style.width = styleWidth + 'px'
            canvas.style.height = styleHeight + 'px'
            console.log(index)
          })
          // 重新渲染 变清晰
          this.scaleRenderAll()
        },
        // 好像改变也不是很明显
        // scaleCanvas(scale) {
        //   // 改变 viewport 大小
        //   this.viewport = this.viewport.clone({
        //     scale: this.pageScale * scale * CSS_UNITS,
        //   });
        //   // 逐个重新渲染
        //   this.renderSinglePage(this.canvasEles[0], 1);
        // },
    
        getPDF() {
          let that = this
          return new Promise(reslove => {
            PDFJS.getDocument(that.src).promise.then(
              function (pdfDoc_) {
                that.pdfDoc = pdfDoc_
                that.totallPage = pdfDoc_.numPages
                that.loading = false
                reslove('success')
              },
              function (reason) {
                console.log(reason.message)
                that.loading = false
                reslove(reason.name)
              }
            )
          })
        },
    
        initRenderOneByOne() {
          for (let pageNum = 1; pageNum <= this.totallPage; pageNum++) {
            let canvas = document.createElement('canvas')
            canvas.setAttribute('id', `pdf-canvas${pageNum}`)
            canvas.setAttribute('class', `pdfcanvas`)
            // alpha 设定 canvas 背景总是不透明,可以加快浏览器绘制透明的内容和图片 初始化出来 canvas 为黑色背景
            // 实际上 导致 重新渲染的时候 闪黑屏
            // let ctx = canvas.getContext("2d", {
            //   alpha: false,
            // });
            let ctx = canvas.getContext('2d')
            this.canvasCtxs.push(ctx)
            this.canvasEles.push(canvas)
            this.wrapEl.appendChild(canvas)
          }
          this.renderSinglePage(this.canvasEles[0], 1)
        },
    
        renderSinglePage(canvas, pageNum) {
          let ctx = this.canvasCtxs[pageNum - 1]
          let that = this
    
          this.pdfDoc.getPage(pageNum).then(function (page) {
            if (that.isFirstTimeRender) that.initView(page, ctx)
    
            if (pageNum === 1) that.getCanvasCSSWH()
    
            canvas.width = that.curCanvasCSSWh.width
            canvas.height = that.curCanvasCSSWh.height
            canvas.style.width = that.curCanvasCSSWh.styleWidth + 'px'
            canvas.style.height = that.curCanvasCSSWh.styleHeight + 'px'
            canvas.style['border'] = '#d6d6d6 solid 1px'
            canvas.style.margin = '9px 0 0 0'
    
            let renderContext = {
              canvasContext: ctx,
              transform: that.transform,
              viewport: that.viewport,
              enableWebGL: false,
              renderInteractiveForms: false
            }
            let renderTask = page.render(renderContext)
    
            renderTask.promise.then(function () {
              if (that.totallPage >= ++pageNum) {
                that.renderSinglePage(that.canvasEles[pageNum - 1], pageNum)
              } else {
                that.pageRendered = true
              }
            })
          })
        },
    
        scaleRenderAll() {
          const len = this.canvasEles.length
          for (let pageNum = 0; pageNum < len; pageNum++) {
            let canvas = this.canvasEles[pageNum]
            let ctx = this.canvasCtxs[pageNum]
    
            let that = this
            this.pdfDoc.getPage(pageNum + 1).then(function (page) {
              canvas.width = that.curCanvasCSSWh.width
              canvas.height = that.curCanvasCSSWh.height
    
              let renderContext = {
                canvasContext: ctx,
                transform: that.transform,
                viewport: that.viewport,
                enableWebGL: false,
                renderInteractiveForms: false
              }
              let renderTask = page.render(renderContext)
    
              renderTask.promise.then(function (context) {
                console.log(context)
              })
            })
          }
        },
    
        getCanvasCSSWH() {
          let outputScale = {
            sx: this.pixelRatio,
            sy: this.pixelRatio,
            scaled: this.pixelRatio !== 1
          }
    
          let pixelsInViewport = this.viewport.width * this.viewport.height
          let maxScale = Math.sqrt(maxCanvasPixels / pixelsInViewport)
    
          if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
            outputScale.sx = maxScale
            outputScale.sy = maxScale
            outputScale.scaled = true
          }
    
          let sfx = (0, this.approximateFraction)(outputScale.sx)
          let sfy = (0, this.approximateFraction)(outputScale.sy)
          const width = (0, this.roundToDivide)(this.viewport.width * outputScale.sx, sfx[0])
          const height = (0, this.roundToDivide)(this.viewport.height * outputScale.sy, sfy[0])
          const styleWidth = (0, this.roundToDivide)(this.viewport.width, sfx[1])
          const styleHeight = (0, this.roundToDivide)(this.viewport.height, sfy[1])
    
          if (this.pixelRatio !== 1) this.transform = [this.pixelRatio, 0, 0, this.pixelRatio, 0, 0]
    
          this.viewWidth = styleWidth + 2
          // 12 加上 canvas border margin 误差?2 + 9 + 1
          this.viewHeight = this.totallPage * (this.viewport.height + 12) + 9
    
          this.curCanvasCSSWh = { width, height, styleWidth, styleHeight }
          return this.curCanvasCSSWh
        },
        approximateFraction(x) {
          if (Math.floor(x) === x) {
            return [x, 1]
          }
    
          var xinv = 1 / x
          var limit = 8
    
          if (xinv > limit) {
            return [1, limit]
          } else if (Math.floor(xinv) === xinv) {
            return [1, xinv]
          }
    
          var x_ = x > 1 ? xinv : x
          var a = 0,
            b = 1,
            c = 1,
            d = 1
    
          while (q < limit) {
            var p = a + c,
              q = b + d
    
            if (q > limit) {
              break
            }
    
            if (x_ <= p / q) {
              c = p
              d = q
            } else {
              a = p
              b = q
            }
          }
    
          var result
    
          if (x_ - a / b < c / d - x_) {
            result = x_ === x ? [a, b] : [b, a]
          } else {
            result = x_ === x ? [c, d] : [d, c]
          }
    
          return result
        },
        roundToDivide(x, div) {
          var r = x % div
          return r === 0 ? x : Math.round(x - r + div)
        },
    
        initView(page, ctx) {
          let devicePixelRatio = window.devicePixelRatio || 1
          let backingStoreRatio =
            ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio ||
            1
          this.pixelRatio = devicePixelRatio / backingStoreRatio
    
          this.viewport = page.getViewport({
            scale: CSS_UNITS
          })
          console.log(this.viewport)
    
          this.pageScale = (this.areaWidth - autoWidth) / this.viewport.width
          let curViewport = page.getViewport({
            scale: this.pageScale * CSS_UNITS
          })
          this.viewport = curViewport
    
          this.isFirstTimeRender = false
        },
    
        drawBorder(canvas, ctx) {
          ctx.save()
          ctx.fillStyle = 'rgb(255, 255, 255)'
          ctx.strokeRect(0, 0, canvas.width, canvas.height)
          ctx.restore()
        }
      }
    }
    </script>
    
    <style scoped>
    .pdf-touch-box {
      padding: 9px;
       calc(100% - 18px);
      height: calc(100% - 18px);
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
    .scale-btn-box {
      position: fixed;
      top: 0;
      left: 0;
      height: 44px;
      display: flex;
      justify-content: space-around;
    }
    .scale-btn {
       25%;
      height: 100%;
      display: inline-block;
      line-height: 1;
      white-space: nowrap;
      cursor: pointer;
      background: #fff;
      border: 1px solid #dcdfe6;
      color: #606266;
      -webkit-appearance: none;
      text-align: center;
      box-sizing: border-box;
      outline: none;
      margin: 0;
      transition: 0.1s;
      font-weight: 500;
      -moz-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
      padding: 12px 20px;
      font-size: 14px;
      border-radius: 4px;
    }
    .pdf-canvas-wrap {
      display: flex;
      flex-direction: column;
      align-items: center;
      overflow: hidden;
      margin-top: 44px;
      padding-top: 9px;
    }
    .pdf-canvas-tips {
      margin-top: 44px;
    }
    </style>

    这样手机打开就也清晰了。

    三、渲染文本图层,支持手势缩放

    要渲染文本图层还需要额外的依赖:

    import { TextLayerBuilder, EventBus } from "pdfjs-dist/web/pdf_viewer";
    import "pdfjs-dist/web/pdf_viewer.css";
     
     只要在渲染的时候添加即可:
    let renderContext = {
      canvasContext: ctx,
      transform: that.transform,
      viewport: that.viewport,
      enableWebGL: false,
      renderInteractiveForms: false,
    };
    let renderTask = page.render(renderContext);
    
    renderTask.promise
      .then(function () {
        if (that.totallPage >= ++pageNum) {
          that.renderSinglePage(that.canvasEles[pageNum - 1], pageNum);
          return page.getTextContent();
        } else {
          that.pageRendered = true;
        }
      })
      .then((textContent) => {
        const textLayerDiv = document.createElement("div");
        textLayerDiv.setAttribute("class", "textLayer");
        textLayerDiv.setAttribute("style", "top: 12px");
        canvas.parentElement.appendChild(textLayerDiv);
    
        var textLayer = new TextLayerBuilder({
          eventBus: new EventBus(),
          textLayerDiv: textLayerDiv,
          pageIndex: pageNum,
          viewport: that.viewport,
        });
    
        textLayer.setTextContent(textContent);
    
        textLayer.render();
      });

     支持手势缩放使用  alloyfinger.js :

    import AlloyFinger from "alloyfinger";
    //包装一下 不然 eslint 报警告
    class FingerTouch {
      constructor(element, options) {
        Object.assign(this, AlloyFinger.prototype);
        AlloyFinger.call(this, element, options);
      }
    }
    alloyfinger 很小,点进去没多少代码,可以学习学习。

     使用:

    this.alloyFinger = new FingerTouch(this.wrapEl, {})
    this.alloyFinger.on('pinch', e => {
      let zoom = e.zoom
      let curScale = this.lastStyleScale * zoom
      if (curScale <= this.pageScale / 2 || curScale >= 5) return
      this.scaleEvent(curScale)
    })
    
    this.alloyFinger.on('pressMove', e => {
      this.viewTop += e.deltaY
      this.viewLeft += e.deltaX
    })

    这里使用了  pressMove 事件,因为 canvas 使用了 absolute 绝对定位,支持 在容器里移动。如果不需要,就让它自适应(通过滚动条移动),就不用 pressMove 事件。

    完整代码: 

    <template>
      <div class="pdf-touch-box">
        <div class="scale-btn-box" :style="{  btnWidth + 'px' }">
          <button class="scale-btn" @click="scaleEvent(3.5)">3.5</button>
          <button class="scale-btn" @click="scaleEvent(2.5)">2.5</button>
          <button class="scale-btn" @click="scaleEvent(1.8)">1.8</button>
          <button class="scale-btn" @click="scaleEvent(1.3)">1.3</button>
          <button class="scale-btn" @click="scaleEvent(1)">1</button>
          <button class="scale-btn" @click="scaleEvent(0.5)">0.5</button>
        </div>
    
        <div
          v-show="!loading"
          class="pdf-canvas-wrap"
          :style="{
            top: viewTop + 'px',
            left: viewLeft + 'px',
             viewWidth + 'px',
            height: viewHeight + 'px'
          }"
        ></div>
        <p class="pdf-canvas-tips" v-show="loading">正在加载...</p>
      </div>
    </template>
    
    <script>
    import * as PDFJS from 'pdfjs-dist'
    console.log(PDFJS)
    
    import { TextLayerBuilder, EventBus } from 'pdfjs-dist/web/pdf_viewer'
    import 'pdfjs-dist/web/pdf_viewer.css'
    console.log(TextLayerBuilder)
    
    // 本地
    // window.pdfjsWorker = require("pdfjs-dist/build/pdf.worker.js");
    
    // cdn 2.8.335  2.6.347 2.5.207
    PDFJS.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.6.347/build/pdf.worker.min.js'
    
    // https://github.com/mozilla/pdf.js/blob/master/examples/node/getinfo.js
    // Requires single file built version of PDF.js -- please run
    // `gulp singlefile` before running the example.
    // const pdfjsLib = require("pdfjs-dist/legacy/build/pdf.js");
    
    const CSS_UNITS = 96.0 / 72.0
    // const PRINT_UNITS = 150 / 72.0;
    
    let userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || ''
    let platform = (typeof navigator !== 'undefined' && navigator.platform) || ''
    let maxTouchPoints = (typeof navigator !== 'undefined' && navigator.maxTouchPoints) || 1
    
    let maxCanvasPixels = 16777216
    // PDF之外占据的宽度 -18 padding -18减去滚动条宽度(不确定)
    let autoWidth = 36
    let textLayerTop = 3
    let scaleInterval = 0.05
    
    let isAndroid = /Android/.test(userAgent)
    let isIOS = /(iPad|iPhone|iPod)(?=;)/.test(userAgent) || (platform === 'MacIntel' && maxTouchPoints > 1)
    ;(function checkCanvasSizeLimitation() {
      if (isIOS || isAndroid) {
        maxCanvasPixels = 5242880
        autoWidth -= 18
        textLayerTop -= 1
        // 手机上面缩放对清晰度影响更小
        scaleInterval = 0.4
      }
    })()
    import AlloyFinger from 'alloyfinger'
    //包装一下 不然 eslint 报警告
    class FingerTouch {
      constructor(element, options) {
        Object.assign(this, AlloyFinger.prototype)
        AlloyFinger.call(this, element, options)
      }
    }
    
    export default {
      data() {
        return {
          src: './static/react-native.pdf',
          loading: true,
          pdfDoc: null,
          boxEl: null,
          wrapEl: null,
          areaWidth: 0,
          btnWidth: 0,
          viewWidth: 0,
          viewHeight: 0,
          pixelRatio: 2,
          isFirstTimeRender: true,
          viewport: null,
          canvasEles: [],
          canvasCtxs: [],
          totallPage: 0,
          pageScale: 1, // pdf 适应窗口产生的 scale
          curCanvasCSSWh: null,
          transform: null,
          pageRenderedNum: 0,
          scaleTimer: null,
          lastStyleScale: 1,
          lastRerenderScale: 1,
          alloyFinger: null,
          viewTop: 0,
          viewLeft: 9,
          textEls: []
        }
      },
      mounted() {
        this.init()
      },
      methods: {
        async init() {
          //禁止下拉刷新
          document.addEventListener(
            'touchmove',
            function (ev) {
              ev.preventDefault()
            },
            { passive: false }
          )
          this.boxEl = document.querySelector('.pdf-touch-box')
          this.wrapEl = document.getElementsByClassName('pdf-canvas-wrap')[0]
          this.btnWidth = this.areaWidth = this.boxEl.clientWidth
    
          const loadingState = await this.getPDF()
          if (loadingState === 'success') {
            this.initRenderOneByOne()
            this.initTouch()
          } else this.boxEl.innerText = loadingState
        },
        initTouch() {
          this.alloyFinger = new FingerTouch(this.wrapEl, {})
          this.alloyFinger.on('pinch', e => {
            let zoom = e.zoom
            let curScale = this.lastStyleScale * zoom
            if (curScale <= this.pageScale / 2 || curScale >= 5) return
            this.scaleEvent(curScale)
          })
    
          this.alloyFinger.on('pressMove', e => {
            this.viewTop += e.deltaY
            this.viewLeft += e.deltaX
          })
        },
        scaleEvent(scale) {
          // 渲染中 不让缩放 也不让重绘
          if (this.pageRenderedNum != this.totallPage || this.totallPage === 0) return
    
          // 没在渲染中 随意缩放
          this.scaleCanvas(scale)
    
          // 说明是第一次事件 或重绘完成 开始计时
          if (this.scaleTimer === null) {
            this.scaleTimer = this.renderDelayer(666)
          }
          //时间间隔内再次触发缩放 重新计时
          clearTimeout(this.scaleTimer)
          this.scaleTimer = this.renderDelayer(666)
        },
        renderDelayer(interval) {
          return setTimeout(() => {
            this.scaleRenderAll()
            this.scaleTimer = null
          }, interval)
        },
        scaleTopLeft(width, height) {
          if (Math.abs(this.viewTop) > height / 2) this.viewTop *= 1 / 2
          if (Math.abs(this.viewLeft) > width / 2) this.viewLeft *= 1 / 2
        },
        scaleCanvas(scale) {
          this.lastStyleScale = scale
          console.log(scale)
          // 改变 viewport 大小
          this.viewport = this.viewport.clone({
            scale: (this.pageScale * CSS_UNITS * scale).toFixed(3)
          })
    
          const { styleWidth, styleHeight } = this.getCanvasCSSWH()
    
          // 计算一下 top left 不然可能会显示到 窗口外面 看不到了
          this.scaleTopLeft(styleWidth, styleHeight)
    
          // 改变CSS canvas 会变模糊
          this.canvasEles.forEach(canvas => {
            // 不修改 width height 不然会重置 canvas 变空白
            canvas.style.width = styleWidth + 'px'
            canvas.style.height = styleHeight + 'px'
          })
        },
        // 使用新渲染的 canvas  替换 缩放过后不清晰的 canvas
        scaleRenderAll() {
          let curInterval = Math.abs(this.lastStyleScale - this.lastRerenderScale)
          let curScaleInterval = scaleInterval
          let isNarrow = this.lastStyleScale < this.lastRerenderScale
          // 如果是变小 变化不大时 清晰度影响更小
          if (isNarrow) curScaleInterval = scaleInterval * 2
    
          console.log('scaleRenderAll', curScaleInterval, curInterval)
          // 变化很小的时候就不计时重新渲染了 清晰度影响不大 1.1 - 1 = 0.10000000000000009
          if (curInterval <= curScaleInterval) return
    
          this.lastRerenderScale = this.lastStyleScale
    
          this.pageRenderedNum = 0
          const len = this.canvasEles.length
          for (let pageNum = 1; pageNum <= len; pageNum++) {
            let newCanvas = document.createElement('canvas')
            let newCtx = newCanvas.getContext('2d', {
              alpha: false
            })
            newCanvas.setAttribute('id', `pdf-canvas${pageNum}`)
    
            this.canvasCtxs[pageNum - 1] = newCtx
    
            let that = this
            this.pdfDoc.getPage(pageNum).then(function (page) {
              that.setCanvasCSSWh.call(that, newCanvas)
    
              let renderTask = that.pageRender.call(that, page, newCtx)
    
              renderTask.promise
                .then(function () {
                  let oldCanvas = that.canvasEles[pageNum - 1]
                  oldCanvas.parentElement.replaceChild(newCanvas, oldCanvas)
                  that.canvasEles[pageNum - 1] = newCanvas
                  that.pageRenderedNum++
    
                  return page.getTextContent()
                })
                .then(textContent => that.textRerender.call(that, pageNum, textContent))
                .catch(e => console.log(e))
            })
          }
        },
    
        getPDF() {
          let that = this
          return new Promise(reslove => {
            PDFJS.getDocument(that.src).promise.then(
              function (pdfDoc_) {
                that.pdfDoc = pdfDoc_
                that.totallPage = 1
                // that.totallPage = pdfDoc_.numPages;
                that.loading = false
                reslove('success')
              },
              function (reason) {
                console.log(reason.message)
                that.loading = false
                reslove(reason.name)
              }
            )
          })
        },
    
        initRenderOneByOne() {
          for (let pageNum = 1; pageNum <= this.totallPage; pageNum++) {
            let canvas = document.createElement('canvas')
            canvas.setAttribute('id', `pdf-canvas${pageNum}`)
            canvas.setAttribute('class', `pdfcanvas`)
            // alpha 设定 canvas 背景总是不透明,可以加快浏览器绘制透明的内容和图片 初始化出来 canvas 为黑色背景
            // 实际上 导致 重新渲染的时候 闪黑屏
            // let ctx = canvas.getContext("2d", {
            //   alpha: false,
            // });
            let ctx = canvas.getContext('2d')
            this.canvasCtxs.push(ctx)
            this.canvasEles.push(canvas)
            //  this.wrapEl.appendChild(canvas);
    
            let pageDiv = document.createElement('div')
            pageDiv.setAttribute('id', 'page-' + pageNum)
            pageDiv.setAttribute('style', 'position: relative;')
            this.wrapEl.appendChild(pageDiv)
            pageDiv.appendChild(canvas)
          }
          this.renderSinglePage(this.canvasEles[0], 1)
        },
    
        renderSinglePage(canvas, pageNum) {
          let ctx = this.canvasCtxs[pageNum - 1]
          let that = this
    
          this.pdfDoc.getPage(pageNum).then(function (page) {
            if (that.isFirstTimeRender) that.initView(page, ctx)
    
            if (pageNum === 1) that.getCanvasCSSWH()
    
            that.setCanvasCSSWh.call(that, canvas)
            let renderTask = that.pageRender.call(that, page, ctx)
    
            renderTask.promise
              .then(function () {
                if (that.totallPage > pageNum) {
                  that.renderSinglePage(that.canvasEles[pageNum], pageNum + 1)
                }
                that.pageRenderedNum++
                return page.getTextContent()
              })
              .then(textContent => that.textRender.call(that, canvas, pageNum, textContent))
          })
        },
        textRerender(pageIndex, textContent) {
          const oldDiv = this.textEls[pageIndex - 1]
    
          const newDiv = document.createElement('div')
          newDiv.setAttribute('class', 'textLayer')
          newDiv.setAttribute('style', `top: ${textLayerTop}px`)
    
          oldDiv.parentElement.replaceChild(newDiv, oldDiv)
          this.textEls[pageIndex - 1] = newDiv
          this.renderTextLayer(newDiv, pageIndex, textContent)
        },
        textRender(canvas, pageIndex, textContent) {
          const textLayerDiv = document.createElement('div')
          textLayerDiv.setAttribute('class', 'textLayer')
          textLayerDiv.setAttribute('style', `top: ${textLayerTop}px`)
          canvas.parentElement.appendChild(textLayerDiv)
          this.textEls[pageIndex - 1] = textLayerDiv
          this.renderTextLayer(textLayerDiv, pageIndex, textContent)
        },
        renderTextLayer(el, index, content) {
          var textLayer = new TextLayerBuilder({
            eventBus: new EventBus(),
            textLayerDiv: el,
            pageIndex: index,
            viewport: this.viewport
          })
    
          textLayer.setTextContent(content)
          textLayer.render()
        },
        setCanvasCSSWh(canvas) {
          canvas.width = this.curCanvasCSSWh.width
          canvas.height = this.curCanvasCSSWh.height
          canvas.style.width = this.curCanvasCSSWh.styleWidth + 'px'
          canvas.style.height = this.curCanvasCSSWh.styleHeight + 'px'
          canvas.style['border'] = '#d6d6d6 solid 1px'
          canvas.style.margin = '0 0 9px 0'
        },
        pageRender(page, ctx) {
          return page.render({
            canvasContext: ctx,
            transform: this.transform,
            viewport: this.viewport,
            enableWebGL: false,
            renderInteractiveForms: false
          })
        },
    
        drawBorder(canvas, ctx) {
          ctx.save()
          ctx.fillStyle = 'rgb(255, 255, 255)'
          ctx.strokeRect(0, 0, canvas.width, canvas.height)
          ctx.restore()
        },
        getCanvasCSSWH() {
          let outputScale = {
            sx: this.pixelRatio,
            sy: this.pixelRatio,
            scaled: this.pixelRatio !== 1
          }
    
          let pixelsInViewport = this.viewport.width * this.viewport.height
          let maxScale = Math.sqrt(maxCanvasPixels / pixelsInViewport)
    
          if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
            // 这里触发会出错
            // outputScale.sx = maxScale;
            // outputScale.sy = maxScale;
            // outputScale.scaled = true;
          }
    
          let sfx = (0, this.approximateFraction)(outputScale.sx)
          let sfy = (0, this.approximateFraction)(outputScale.sy)
          const width = (0, this.roundToDivide)(this.viewport.width * outputScale.sx, sfx[0])
          const height = (0, this.roundToDivide)(this.viewport.height * outputScale.sy, sfy[0])
          const styleWidth = (0, this.roundToDivide)(this.viewport.width, sfx[1])
          const styleHeight = (0, this.roundToDivide)(this.viewport.height, sfy[1])
    
          if (this.pixelRatio !== 1) this.transform = [this.pixelRatio, 0, 0, this.pixelRatio, 0, 0]
    
          this.viewWidth = styleWidth + 2
          // 12 加上 canvas border margin 误差?2 + 9 + 1
          this.viewHeight = this.totallPage * (this.viewport.height + 12) + 9
    
          this.curCanvasCSSWh = { width, height, styleWidth, styleHeight }
          return this.curCanvasCSSWh
        },
        approximateFraction(x) {
          if (Math.floor(x) === x) {
            return [x, 1]
          }
    
          var xinv = 1 / x
          var limit = 8
    
          if (xinv > limit) {
            return [1, limit]
          } else if (Math.floor(xinv) === xinv) {
            return [1, xinv]
          }
    
          var x_ = x > 1 ? xinv : x
          var a = 0,
            b = 1,
            c = 1,
            d = 1
          // eslint-disable-next-line
          while (true) {
            var p = a + c,
              q = b + d
    
            if (q > limit) {
              break
            }
    
            if (x_ <= p / q) {
              c = p
              d = q
            } else {
              a = p
              b = q
            }
          }
    
          var result
    
          if (x_ - a / b < c / d - x_) {
            result = x_ === x ? [a, b] : [b, a]
          } else {
            result = x_ === x ? [c, d] : [d, c]
          }
    
          return result
        },
        roundToDivide(x, div) {
          var r = x % div
          return r === 0 ? x : Math.round(x - r + div)
        },
    
        initView(page, ctx) {
          let devicePixelRatio = window.devicePixelRatio || 1
          let backingStoreRatio =
            ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio ||
            1
          this.pixelRatio = devicePixelRatio / backingStoreRatio
    
          this.viewport = page.getViewport({
            scale: CSS_UNITS
          })
    
          this.pageScale = (this.areaWidth - autoWidth) / this.viewport.width
          let curViewport = page.getViewport({
            scale: this.pageScale * CSS_UNITS
          })
          this.viewport = curViewport
    
          this.isFirstTimeRender = false
        }
      }
    }
    </script>
    
    <style scoped>
    .pdf-touch-box {
      padding: 9px;
       calc(100% - 18px);
      height: calc(100% - 18px);
      position: relative;
    }
    .scale-btn-box {
      position: fixed;
      top: 0;
      left: 0;
      height: 44px;
      display: flex;
      justify-content: space-around;
      z-index: 99;
    }
    .scale-btn-box::after {
      content: '';
       100%;
      height: 100%;
      position: absolute;
      background: #fff;
      top: 0;
      left: 0;
      filter: blur(18px);
      opacity: 0.8;
    }
    .scale-btn {
      position: relative;
      z-index: 2;
       25%;
      height: 100%;
      display: inline-block;
      line-height: 1;
      white-space: nowrap;
      cursor: pointer;
      background: #fff;
      border: 1px solid #dcdfe6;
      color: #606266;
      -webkit-appearance: none;
      text-align: center;
      box-sizing: border-box;
      outline: none;
      margin: 0;
      transition: 0.1s;
      font-weight: 500;
      -moz-user-select: none;
      -webkit-user-select: none;
      -ms-user-select: none;
      padding: 12px 20px;
      font-size: 14px;
      border-radius: 4px;
    }
    .pdf-canvas-wrap {
      display: flex;
      flex-direction: column;
      align-items: center;
      overflow: hidden;
      margin-top: 44px;
      padding-top: 9px;
      position: absolute;
    }
    .pdf-canvas-tips {
      margin-top: 44px;
    }
    </style>
  • 相关阅读:
    Android开发CheckBox控件,全选,反选,取消全选
    华为OJ平台——放苹果(典型整数划分问题)
    华为OJ平台——查找组成一个偶数最接近的两个素数
    华为OJ平台——输出最小的k个数
    华为OJ平台——完美数
    华为OJ平台——杨辉三角的变形
    Inconsistant light map between PC and Mobile under Unity3D
    How to Effectively crack .JAR Files?
    OpenGL 3 and OpenGL 4 with GLSL
    Dynamic Ambient Occlusion and Indirect Lighting
  • 原文地址:https://www.cnblogs.com/jiayouba/p/14969611.html
Copyright © 2011-2022 走看看