最近公司做了一个我的项目,次要性能是把印章在PDF上拖拽定位的。

这个性能次要两个点:

  1. 块的拖拽即时显示印章内容;
  2. PDF回显(因波及多人操作,回显包含之前人所签的最终成果)。

应用的pdf.js@2.2.28 读取PDF文件,通过canvas 以图像的模式回显在页面上(因为不是单纯的显示,还有很多交互,最终抉择了这个计划)

开始所有都挺顺利的,拖拽计算坐标,PDF回显封装,印章属性设置。。。

PDF回显电子签章

在开发过程中没成想PDF回显深了一个大坑,开始是曾经有印章不能回显,在网上搜寻要改pdf.work.js源码,搜寻上面这个判断,都注掉就行了

// if (data.fieldType === 'Sig') {//      data.fieldValue = null;//      _this3.setFlags(_util.AnnotationFlag.HIDDEN);//    }

尽管可能显示印章了,但在翻页时会产生只显示一半色彩的状况,钻研了好几天,更换pdf.js 版本、缩小canvas 配置项都没胜利。
忽然在一次测试中发现,要是上来就把所有都渲染进去如同就没问题,随即用了一个比拟low的办法,就是上来就把所有pdf都渲染DOM,但不显示进去(display:none),只有与以后页码雷同的才显示,目前测试还没发现问题。

另,现有几个小问题:

  1. 布局时尽量用 padding、box-sizing: content-box;,用margin 有时取坐标数据不好管制;
  2. 因为找的例子是适配挪动端的,外面有个参数window.devicePixelRatio,这个在mac 下通常为2,因为挪动端用这个计算了宽度,在mac 下应用的时候根底坐标都*2了,PC 无脑改成1;

对于 window.devicePixelRatio 能够具体参考:
张鑫旭-设施像素比devicePixelRatio简略介绍

附上pdf 的例子
外围渲染pdf 的局部也是网上收集的,依据业务再进行了批改;

pdfjs-fix-sig 只是把电子签章那块解决了,顺手公布在本人的私服上了,npm 上必定没有,失常引到的名称是 pdfjs-dist

<template>    <div class="pdf-box" id="pdf-box">        <div class="pdf-page-btn-box">            <y-button @click="handleSub()" :disabled="disabledSub">上一页</y-button>            <div class="input-box">                <input class="input-wrap"                       :title="pdfCurrentPage"                       v-model="tunePage"                       @blur="pageChange"                       @keyup.13="pageChange"/>            </div>            <div class="num-page-box"> / {{pdf_pages}}</div>            <y-button @click="handleAdd()" :disabled="disabledAdd">下一页</y-button>        </div>        <div class="pdf-view-wrap">            <div ref="canvasBox"                 class="canvas-box" id="canvas-box"                 :style="{width:`${pdf_div_width}px`,margin:'0 auto'}">                <slot name="drag-collection-box"></slot>                <!--                <canvas ref="canvasPage" :id="'the-canvas'+pdfCurrentPage"-->                <!--                        style="border: 1px #eeeeee solid;"></canvas>-->                <template v-for="index in pdf_pages">                    <canvas ref="canvasPage" :id="'the-canvas'+index"                            v-show="index === Number(pdfCurrentPage)"                            style="border: 1px #eeeeee solid;"></canvas>                </template>            </div>        </div>        <ul v-if="sealPlaceShow" class="seal-place" ref="sealPlace" :style="`height: ${canvasHeight}px`">            <li v-for="index in 5"></li>        </ul>    </div></template><script>    // 筹备PDF文件和签章数量、类型    import Vue from "vue";    let PDFJS = require('pdfjs-fix-sig');    PDFJS.GlobalWorkerOptions.workerSrc = Vue.prototype.GLOBAL.dataUrl + '/staticFront/script/pdfjs-fix-sig/build/pdf.worker.min.js';    export default {        name: "pdf-box",        props: {            pdfSrc: {                type: String            },            sealPlaceShow: {                type: Boolean,                default: false            }        },        data() {            return {                pdf_scale: 1,//pdf放大系数 (1.33 回显的pdf 章是100,但思考到回显的pdf是按595*841展现【所对应应的章是75*75】),A4: 595 * 841                pdf_pages: null,                pdfDoc: null,                pdf_div_width: '595',                pdf_src: null,                pdfCurrentPage: 1,                tunePage: 1,                disabledAdd: false,                disabledSub: true, // 初始不能减页                canvasWidth: 0,                canvasHeight: 0,                sealPlaceFlag: false,            }        },        watch: {            sealPlaceShow: {                handler(val) {                    this.sealPlaceFlag = val;                },                immediate: true            }        },        created() {            if (this.pdfSrc.length > 0) {                this.getPdfUrl();            } else {                this.$Message.error({content: '请应用正确的PDF文件'});            }        },        methods: {            getPdfUrl() {  //取得pdf                //例子:加载pdf文件示例;                this.loadFile(this.pdfSrc);                //线上申请 文件流(2020年11月03日 08:55:02 未测试)                // this.$http.get(this.pdfSrc).then((res) => {                //     this.pdf_src = res.url;                //     this.loadFile(this.pdf_src)                // }, (err) => {                //     console.log(err);                // });            },            //初始化pdf            loadFile(url) {                let _this = this;                let loadingTask = PDFJS.getDocument(url);                loadingTask.promise.then((pdf) => {                    _this.pdfDoc = pdf;                    _this.pdf_pages = _this.pdfDoc.numPages; // 总页数                    _this.$nextTick(() => {                        _this.setAddSubBtn();                        for (let i = 1; i <= _this.pdf_pages; i++) {                            _this.renderPage(i)                        }                    })                })            },            //渲染pdf页            renderPage(num) {                let _this = this;                this.pdfDoc.getPage(num)                    .then((page) => {                        let canvas = document.getElementById('the-canvas' + num);                        let ctx = canvas.getContext('2d');                        // let dpr = window.devicePixelRatio || 1; // mac 的 window.devicePixelRatio 为 2                        let dpr = 1;                        let bsr = ctx.webkitBackingStorePixelRatio ||                            ctx.mozBackingStorePixelRatio ||                            ctx.msBackingStorePixelRatio ||                            ctx.oBackingStorePixelRatio ||                            ctx.backingStorePixelRatio || 1;                        let ratio = dpr / bsr;                        let viewport = page.getViewport({scale: this.pdf_scale});                        canvas.width = viewport.width * ratio;                        canvas.height = viewport.height * ratio;                        _this.canvasWidth = canvas.width;                        _this.canvasHeight = canvas.height;                        _this.$emit('setCanvasAttr', {                            canvasWidth: _this.canvasWidth,                            canvasHeight: _this.canvasHeight,                            pdfCurrentPage: _this.pdfCurrentPage,                            numPages: this.pdfDoc.numPages                        });                        canvas.style.width = viewport.width + 'px';                        _this.pdf_div_width = viewport.width; // 定位在左边时须要canvas的宽度进行计算                        canvas.style.height = viewport.height + 'px';                        ctx.setTransform(ratio, 0, 0, ratio, 0, 0);                        let renderContext = {                            canvasContext: ctx,                            viewport: viewport                        };                        page.render(renderContext).promise.then((res) => {                            // console.log(this.getBounding('canvasPage'));                            // console.log(this.getEleClass('layout-menu-left').clientWidth);                            // console.log(this.getBounding('canvasPage').right - this.getEleClass('layout-menu-left').clientWidth + 'px');                        });                    });            },            // 减页            handleSub() {                this.pdfCurrentPage--;                this.tunePage = this.pdfCurrentPage;                this.setAddSubBtn();            },            // 加页            handleAdd(_str) {                this.pdfCurrentPage++;                this.tunePage = this.pdfCurrentPage;                this.setAddSubBtn();            },            // 判断翻页按钮是否禁用并渲染canvas            setAddSubBtn() {                this.disabledSub = this.pdfCurrentPage <= 1;                this.disabledAdd = this.pdfCurrentPage >= this.pdf_pages;                // this.renderPage(this.pdfCurrentPage);                this.$emit('setCanvasAttr', {                    canvasWidth: this.canvasWidth,                    canvasHeight: this.canvasHeight,                    pdfCurrentPage: this.pdfCurrentPage,                    numPages: this.pdfDoc.numPages                });            },            pdfChangePage() {                this.$emit('pdf-change-page', true);            },            getEleClass(_str) {                return document.getElementsByClassName(_str)[0];            },            getBounding(_str) {                return this.$refs[_str].getBoundingClientRect();            },            pageChange() {                // 输出字符容错                this.pdfCurrentPage = isNaN(Number(this.tunePage)) ? 1 : Number(this.tunePage);                // 输出大数容错                this.pdfCurrentPage = this.tunePage > this.pdf_pages ? this.pdf_pages : this.tunePage;                this.setAddSubBtn();            }        }    }</script>
父级援用
<pdfBox ref="pdfBox"                    :pdfSrc="doSignData.originFile"                        :canvasHeight="canvasHeight"                    :sealPlaceShow="true">                <div slot="drag-collection-box" v-if="$route.meta.viewType !== 'view'">                    <!-- 这个插槽放在pdf 上显示的印章 -->                </div>            </pdfBox>
最初放上款式
@drag-block-size: 75px;.e-sign-wrap {  .pdf-page-btn-box {    position: absolute;    text-align: right;    font-size: 12px;    right: 0;    z-index: 5;    padding: 7px;    color: #42474f;    .input-box, .num-page-box {      display: inline-block;      height: 22px;      line-height: 22px;    }    .input-box {      width: 30px;      .input-wrap {        width: 100%;        padding: 0 2px;        font-size: 12px;        border: 1px transparent solid;        border-bottom: 1px #dddee1 solid;        border-radius: 2px;        color: #495060;        text-align: right;        &:focus {          outline: none;        }      }    }    .num-page-box {      vertical-align: middle;    }  }  .pdf-box {    float: left;    font-size: 0;    position: relative;    .pdf-view-wrap {      position: relative;      display: inline-block;      .drag-box {        li {          cursor: move;          user-select: none;          position: absolute;          opacity: .7;          z-index: 5;          border: 1px dashed transparent;          .party {            color: #ffffff;            height: 24px;            line-height: 24px;            padding-left: 5px;            width: 100%;            font-size: 12px;          }          .content {            width: 98px;            height: 49px;            line-height: 18px;            padding: 5px;            font-size: 14px;            text-align: center;            display: table-cell;            vertical-align: middle;            word-break: break-all;            .sign-type {              font-size: 12px;            }          }          &.drag-block {            width: @drag-block-size;            height: @drag-block-size;            background-color: #cfddfb;            .party {              background-color: #9bbcff;            }            .content {              color: #78a5ff;            }          }          &.place-block {            width: @drag-block-size;            height: @drag-block-size;            background-color: #ffcccc;            .party {              background-color: #f09494;            }            .content {              color: #f09494;              line-height: 16px;              padding: 0;              font-size: 12px;            }            //.btn-box {            //  .btn {            //    width: 100%;            //  }            //}          }          &.drag-block, &.place-block {            &.checked::after {              content: '';              width: 100%;              height: 100%;              border: 2px #004CC0 solid;              display: block;              position: absolute;              top: 0;              left: 0;              z-index: 0;            }            img {              -webkit-user-drag: none;            }          }          .btn-box {            cursor: pointer;            .btn {              //float: left;              width: 100%;              font-size: 12px;              text-align: center;              height: 24px;              line-height: 24px;              background-color: #004cc0;              color: #ffffff;            }          }        }      }      canvas {        width: 595px;        height: 842px;        //max-width: 595px;        //max-height: 841px;      }    }    .seal-place {      display: inline-block;      position: absolute;      right: -25px;      min-height: 841px;      li {        width: 5px;        height: 100%;        display: inline-block;        border: 1px #eee solid;        border-left: none;      }    }  }}