最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • fabric.js实现可视化签章以及遮罩打印的功能

    正文概述 掘金(OrzR3)   2021-03-24   722

    首先,是因为这样一个需求,我开始尝试使用fabric.js

    公司有个项目,是可信电子凭证可视化签章

    后来,又一次用到了fabric.js是档案管理平台的项目

    fabric.js实现可视化签章以及遮罩打印的功能

    fabric.js实现可视化签章以及遮罩打印的功能

    了解一门新技术,直接看官网和相关文档。

    **fabricjs官网在此:**fabricjs.com/

    官网首页,写在这样一段话:

    Fabric.js是一个强大而简单的Javascript HTML5 Canvas库

    然后看了相关文档,了解到我们能通过使用它实现在canvas上创建,填充图形,给图形填充渐变颜色。组合图形(包括组合图形,图形文字,图片等)等一系列功能。

    简单来说,我们可以通过使用Fabric从而以较为简单的方式,实现较为复杂的Canvas功能。

    知道和做到之间,有一条天然的鸿沟。

    有时,人们了解到前人的经验踩过的坑,但是仍然不可避免的掉进这些坑里。自己掉进这些坑里,再爬出来,才最终学习到这些经验,最终避开这些坑。

    了解到其基础概览,和应用场景之后,准备快速上手。

    在vue项目中引入服务

    npm install fabric
    import { fabric } from 'fabric'
    

    首先做了一个demo用来实现在pdf预览的区域上拖拽图片。

    关于pdf文件预览,之前用的pdf.js基于html的pdf阅读器,从官网下载静态资源,放到项目的static静态资源文件夹里面。

    使用pdf.js已经写好的viewer.html页面来预览。

    static/pdf/web/viewer.html?file=' + encodeURIComponent(pdf)
    

    使用iframe标签去显示。然后,封装成一个公共工作,在需要的地方,直接调用。

    组件代码:

    <template>
      <div class="pdf">
        <div class="box-card pdf-viewer">
          <iframe
            :src="'static/pdf/web/viewer.html?file=' + encodeURIComponent(pdf)"
            :height="height"
            width="100%"
            frameborder="0"
          ></iframe>
        </div>
      </div>
    </template>
    <script>
    export default {
      name: "PdfDetail",
      components: {},
      props: {
        pdf: {
          type: String,
          default: "",
        },
        height: {
          type: Number,
          default: 560
        }
      },
      data() {
        return {};
      },
      watch: {},
      computed: {},
      methods: {},
      created() {},
      mounted() {},
    };
    </script>
    <style scoped>
    .wrapper {
    }
    </style>
    

    这里只是顺带说了一下pdf.js预览的方法,我并没有采用这种方法去实现pdf预览功能。

    因为,不仅要预览,还需要将pdf预览区域转换成canvas画布,然后在画布上实现图片拖拽位置的功能,并获取坐标。

    我采用的是vue-pdf组件

    GitHub地址:

    github.com/FranckFreib…

    npm install --save vue-pdf
    
    <template>
      <pdf src="./static/relativity.pdf"></pdf>
    </template>
    <script>
    import pdf from 'vue-pdf'
    export default {
      components: {
        pdf
      }
    }
    

    以下是demo的核心代码

    // 初始化画布对象
    new fabric.Canvas('canvas')
    //赋予一个变量,并且添加了双击事件,通过双击事件删除canvas画布上添加的内容
    this.canvas = new fabric.Canvas('canvas')
    this.canvas.on('mouse:dblclick', (e) => {
        let items = this.canvas.getObjects()
        items = items.filter((item) => item.width > 1 && item.height > 1)
        let itemIdx = items.indexOf(e.target)
        this.canvas.remove(this.canvas.item(itemIdx));
        this.canvas.renderAll();
    })
    

    在created钩子函数中,设置fabric的对象拖拽框

    fabric.Object.prototype.setControlsVisibility({
          bl: false, // 左下
          br: false, // 右下
          mb: false, // 下中
          ml: false, // 中左
          mr: false, // 中右
          mt: false, // 上中
          tl: false, // 上左
          tr: false, // 上右
          mtr: false, // 旋转控制键
    });
    

    通过图片路径,往画布上添加图片的方法

    let imgCoord = fabric.Image.fromURL(imgUrl, (img) => {
        img.scale(1).set({
            crossOrigin: 'anonymous',
            left: 0,
            top: 0,
        })
    this.canvas.add(img).setActiveObject(img)
    })
    

    通过图片对象,往画布上添加图片的方法

    let image= new Image()
    image.src = imgUrl
    image.crossOrigin = 'Anonymous';
    image.onload = () => {
          fabric.Image.fromObject(imgl,(img) => {
              img.scale(1).set({
                  crossOrigin: 'anonymous',
                  left,
                  top,
                  width,
                  height,
                  scaleX,
                  scaleY,
              })
            this.canvas.add(img).setActiveObject(img)
              this.canvas.renderAll()
          })
      }
    

    除了添加图片,还可以添加文本框,并且设置文字的颜色字体大小等等。

    需要注意的是fontSize参数必须为Number类型。

    let attributeObject = {
        fill,
        fontFamily,
        fontWeight,
        textAlign,
        lineHeight,
        width,
        splitByGrapheme:true,
        height,
        fontSize:,
        originX: 'center',
        originY: 'center',
    }
    var obj = new fabric.Textbox(text, attributeObject)
    var group = new fabric.Group([obj], {
        left,
        top,
    })
    this.canvas.add(group)
    this.canvas.renderAll()
    
    • 为了最终拿到画布上所有对象的属性,以及坐标。我将这些属性和坐标,放到了一个json对象数组里面,保存起来。
    • 图片和文字,除了能够拖拽,文字框还要求,能够改变字体颜色大小等等。
    • 我加了字体颜色大小等属性的选择框,做了数据双向绑定。
    • 每当json对象数组改变的时候,我就清空画布上的所有对象,然后从json对象数组里面拿到保存的属性和坐标,在画布上重新渲染。
    //清空画布
    this.canvas.clear()
    

    获取画布上所有对象的坐标

    getImgPosition() {
        if (!this.canvas) return
        this.imgcoordinate = []
        let items = this.canvas.getObjects()
        items = items.filter((item) => item.width > 1 && item.height > 1)
        items.forEach((item, index) => {
            let itemcoord = {
                floorIndex: index,
                tl: {
                    x: item.aCoords.tl.x,
                    y: item.aCoords.tl.y,
                },
                tr: {
                    x: item.aCoords.tr.x,
                    y: item.aCoords.tr.y,
                },
                bl: {
                    x: item.aCoords.bl.x,
                    y: item.aCoords.bl.y,
                },
                br: {
                    x: item.aCoords.br.x,
                    y: item.aCoords.br.y,
                },
            }
            this.imgcoordinate.push(itemcoord)
        })
        this.xycoordinate = this.imgcoordinate.map((item) => item.tl)
    }
    

    最后说一下,在canvas画布通过fabric.js添加马赛克的方法

    首先,初始化canvas画布对象的时候,增加鼠标事件监听的方法

    this.canvas = new fabric.Canvas("canvas");
    this.canvas.on("mouse:down", function (e) {
      that.mousedown(e);
    });
    //鼠标抬起事件
    this.canvas.on("mouse:up", function (e) {
      that.mouseup(e);
    });
    // 移动画布事件
    this.canvas.on("mouse:move", function (e) {
      that.mousemove(e);
    });
    
    • 鼠标点击mousedown事件时,记录下画布上点的位置,
    • 鼠标移动后抬起mouseup事件时,记录下画布上点的最终位置,
    • 这样,就可以算出鼠标拖拽的矩形的初始位置x,y坐标,以及矩形的宽高。
    let mouse = this.canvas.getPointer(e.e);
    

    接下来是最关键的,实现马赛克的方法,这个花了很长时间。

    • 在初始化canvas画布对象的时候,需要通过getContext() 方法返回一个用于在画布上绘图的环境。
    • 然后传一个图片路径imgUrl,通过drawImage画出底图。
    • 具体生成马赛克的方法,在setColor中,是通过context对象的getImageData方法,获取图片数据。根据设置的马赛克方块大小,通过rgb的颜色设置,模糊掉底图上的图片,实现遮罩的效果。
    let Img = new Image();
    Img.src = imgUrl;
    that.bgImage = Img;
    Img.onload = () => {
        that.context.drawImage(Img, 0, 0);
        that.context.save();
    };
    

    以下,是画矩形方框,并且填充马赛克,最终实现马赛克的方法

    drawMake() {
      if(!this.canvas)return;
      this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
      this.context.drawImage(this.bgImage, 0, 0);
      this.context.save();
      // if (this.canvas) this.canvas.clear();
      this.makeList.forEach((item) => {
        let { beginX, beginY, w, h } = item;
        this.makeGrid(beginX, beginY, w, h);
      });
    },
    makeGrid(beginX, beginY, rectWidth, rectHight) {
      const row = Math.round(rectWidth / this.squareEdgeLength) + 1;
      const column = Math.round(rectHight / this.squareEdgeLength) + 1;
      for (let i = 0; i < row * column; i++) {
        let x = (i % row) * this.squareEdgeLength + beginX;
        let y = parseInt(i / row) * this.squareEdgeLength + beginY;
        this.setColor(x, y);
      }
    },
    setColor(x, y) {
      const imgData = this.context.getImageData(
        x,
        y,
        this.squareEdgeLength,
        this.squareEdgeLength
      ).data;
      let r = 0,
        g = 0,
        b = 0;
      for (let i = 0; i < imgData.length; i += 4) {
        r += imgData[i];
        g += imgData[i + 1];
        b += imgData[i + 2];
      }
      r = Math.round(r / (imgData.length / 4));
      g = Math.round(g / (imgData.length / 4));
      b = Math.round(b / (imgData.length / 4));
      this.drawRect(
        x,
        y,
        this.squareEdgeLength,
        this.squareEdgeLength,
        `rgb(${r}, ${g}, ${b})`,
        2,
        `rgb(${r}, ${g}, ${b})`
      );
    },
    drawRect(
      x,
      y,
      width,
      height,
      fillStyle,
      lineWidth,
      strokeStyle,
      globalAlpha
    ) {
        this.context.beginPath();
        this.context.rect(x, y, width, height);
        this.context.lineWidth = lineWidth;
        this.context.strokeStyle = strokeStyle;
        fillStyle && (this.context.fillStyle = fillStyle);
        globalAlpha && (this.context.globalAlpha = globalAlpha);
        this.context.fill();
        this.context.stroke();
    },
    

    除了fabric.js之外,为了实现遮罩打印的功能。还用到了 html2canvas 和 jsPDF的方法,在此不一一赘述,直接放出遮罩打印的组件完整代码。

    实现了基本的业务需求之后,我还做了一些优化,譬如撤销和回退的功能。增加了属性设置弹框,通过拖动滑块选择马赛克方块的大小。通过driver.js实现帮助提示,操作指引。

    这里,主要是记录了实现业务需求的解决思路,以及踩坑指南。

    参考了博客园的两篇文章:

    Canvas实用库Fabric.js使用手册

    www.cnblogs.com/aaron911/p/…

    Vue PDF文件预览vue-pdf

    www.cnblogs.com/steamed-twi…

    遮罩打印的组件,完整代码

    <template>
      <div class="wrapper">
        <div class="web-file">
          <!-- 操作栏 -->
          <div class="operate-box nowrap flex justify-between">
            <div class="flex btn-list">
              <div class="page-btn">
                <el-button
                  type="default"
                  @click="changePdfPage(0)"
                  :disabled="currentPage == 1"
                  size="mini"
                >
                  上一页
                </el-button>
                <div class="page-count">
                  <div v-show="pageCount">{{ currentPage }} / {{ pageCount }}</div>
                </div>
                <el-button
                  type="default"
                  @click="changePdfPage(1)"
                  :disabled="currentPage == pageCount"
                  size="mini"
                >
                  下一页
                </el-button>
              </div>
              <div id="mask-print">
                <el-button type="default" @click="printPdf" icon="el-icon-printer" size="mini">
                  打印
                </el-button>
              </div>
              <div id="mask-setting">
                <el-button
                  type="default"
                  @click="attributeEdit"
                  icon="el-icon-setting"
                  size="mini"
                >
                  遮罩属性设置
                </el-button>
              </div>
              <div id="mask-add">
                <el-button type="default" @click="addMask" size="mini">添加遮罩</el-button>
              </div>
              <el-button type="text" @click="guide" icon="el-icon-info">
                帮助
              </el-button>
            </div>
            <div class="flex btn-list">
              <div id="mask-back">
                <el-button
                  type="default"
                  @click="stepBack"
                  icon="el-icon-arrow-left"
                  size="mini"
                >
                  回退
                </el-button>
              </div>
              <div id="mask-clear">
                <el-button
                  type="danger"
                  @click="clearClean"
                  icon="el-icon-refresh-left"
                  size="mini"
                >
                  撤销
                </el-button>
              </div>
              <el-button @click="goBack" size="mini">返回原文页</el-button>
            </div>
          </div>
          <!-- pdf翻页 -->
          <div v-show="pageCount && pageCount > 0"></div>
          <div class="pdf-box" id="pdf" ref="baseMap" v-if="showPdfBoxFlag">
            <!-- pdf预览 -->
            <pdf
              ref="pdf"
              :src="pdfsrc"
              :page="currentPage"
              @num-pages="pageCount = $event"
              @page-loaded="pageLoaded"
              @loaded="loadPdfHandler"
            ></pdf>
            <div
              class="manager_detail"
              id="manager_detail"
              :class="{ 'accurate-choice': accurateChoiceFlag }"
            >
              <!-- 画布 -->
              <canvas
                id="canvas"
                ref="imgContent"
                :width="canvasObj.width"
                :height="canvasObj.height"
                style="
                  position: absolute;
                  width: 100%;
                  height: 100%;
                  left: 0;
                  top: 0;
                  cursor: crosshair;
                "
              ></canvas>
            </div>
          </div>
        </div>
        <attribute-set
          :
          @close="attribute.show = false"
          @ok="setAttribute"
          v-if="attribute.show"
          ref="attribute"
        />
      </div>
    </template>
    <script>
    import { fabric } from "fabric";
    import pdf from "vue-pdf";
    import html2canvas from "html2canvas";
    import jsPDF from "jspdf";
    import AttributeSet from "./AttributeSet";
    // 引导页的功能
    import Driver from "driver.js"; // import driver.js
    import "driver.js/dist/driver.min.css"; // import driver.js css
    import steps from "./steps";
    export default {
      components: {
        pdf,
        AttributeSet,
      },
      props: {
        pdfsrc: {
          type: String,
          default: "",
        }
      },
      data() {
        return {
          canvas: null,
          context: "",
          bgImage: null,
          pageCount: 1, // pdf文件总页数
          currentPage: 1,
          clickFlag: false,
          clickTimer: -1,
          canvasObj: {
            width: 0,
            height: 0,
          },
          json: [],
          basecoordinate: [], //基础坐标数组
          xycoordinate: [], // 左上角的坐标数组
          isMasic: true,
          squareEdgeLength: 20, //马赛克大小
          mouse: {
            started: false,
            x: 0,
            y: 0,
          },
          accurateChoiceFlag: false,
          imgUrl: "",
          // maskPic: "/static/img/fabric/mask.png",
          makeGridObject: {
            beginX: 0,
            beginY: 0,
          },
          makeList: [],
          fillColor: "",
          attribute: {
            show: false,
            title: "属性设置",
            loading: false,
            style: "1", //0代表颜色,1代表马赛克
          },
          driver: null,
          showPdfBoxFlag: false,
        };
      },
      computed: {},
      created() {
        fabric.Object.prototype.setControlsVisibility({
          bl: false, // 左下
          br: false, // 右下
          mb: false, // 下中
          ml: false, // 中左
          mr: false, // 中右
          mt: false, // 上中
          tl: false, // 上左
          tr: false, // 上右
          mtr: false, // 旋转控制键
        });
        this.showPdfBoxFlag = true;
      },
      mounted() {
        this.driver = new Driver({
          //此处为api
          animate: true,
          opacity: 0.5,
          allowClose: false,
          doneBtnText: "完成",
          closeBtnText: "关闭",
          nextBtnText: "下一步",
          prevBtnText: "上一步",
          onReset: (Element) => {
            //这里写逻辑回调
          },
        });
      },
      methods: {
        goBack() {
          this.$emit('back');
        },
        guide() {
          this.driver.defineSteps(steps);
          this.driver.start();
        },
        setAttribute(item) {
          this.attribute.style = item.styleValue;
          this.fillColor = item.styleValue == "0" ? "#fff" : "";
          this.squareEdgeLength = item.maskValue;
          this.drawMake();
        },
        attributeEdit() {
          this.attribute.show = true;
          this.$nextTick(() => {
            let item = {
              styleValue: this.attribute.style,
              maskValue: this.squareEdgeLength,
            };
            this.$refs.attribute.initData(item);
          });
        },
        addMask() {
          this.mouse.started = true;
          this.initCanvasObjAndEvent();
          this.accurateChoiceFlag = true;
        },
        // 返回上一步
        stepBack() {
          if (this.makeList.length > 0) {
            this.makeList.splice(this.makeList.length - 1, 1);
            this.drawMake();
          }
        },
        // 初始化画布对象
        initCanvasObjAndEvent() {
          if (!this.canvas) {
            this.canvas = new fabric.Canvas("canvas");
            let imgContent = this.$refs.imgContent;
            this.context = imgContent.getContext("2d");
            let that = this;
            this.pageTransformedIntoCanvas((pageData, PDF) => {
              let Img = new Image();
              Img.src = pageData;
              that.bgImage = Img;
              Img.onload = () => {
                that.context.drawImage(Img, 0, 0);
                that.context.save();
              };
            });
            this.canvas.on("mouse:down", function (e) {
              that.mousedown(e);
            });
            //鼠标抬起事件
            this.canvas.on("mouse:up", function (e) {
              that.mouseup(e);
            });
            // 移动画布事件
            this.canvas.on("mouse:move", function (e) {
              that.mousemove(e);
            });
          }
        },
        pageLoaded(e) {
          this.currentPage = e;
          this.canvasObj.width = document.getElementById("pdf").offsetWidth;
          this.canvasObj.height = document.getElementById("pdf").offsetHeight;
        },
        // pdf加载时
        loadPdfHandler(e) {
          // this.currentPage = 1; // 加载的时候先加载第一页
        },
        // 改变PDF页码,val传过来区分上一页下一页的值,0上一页,1下一页
        changePdfPage(val) {
          if (this.clickFlag) return;
          this.clickFlag = true;
          this.clickTimer = setTimeout(() => {
            this.clickFlag = false;
          }, 500);
          if (val === 0 && this.currentPage > 1) {
            this.currentPage--;
          }
          if (val === 1 && this.currentPage < this.pageCount) {
            this.currentPage++;
          }
          if(this.canvas)this.canvas.clear();
          this.canvas = null;
          this.showPdfBoxFlag = false;
          this.$nextTick(() =>{
            this.showPdfBoxFlag = true;
          })
        },
        // 鼠标事件
        mousedown(e) {
          if (!this.mouse.started) {
            return false;
          }
          let mouse = this.canvas.getPointer(e.e);
          // this.mouse.started = true;
          let x = mouse.x;
          let y = mouse.y;
          this.mouse = {
            ...this.mouse,
            x,
            y,
          };
          this.makeGridObject = {
            beginX: x,
            beginY: y,
          };
        },
        mousemove(e) {
          if (!this.mouse.started) {
            return false;
          }
          var mouse = this.canvas.getPointer(e.e);
          var w = Math.abs(mouse.x - this.mouse.x),
            h = Math.abs(mouse.y - this.mouse.y);
          if (!w || !h) {
            return false;
          }
        },
        mouseup(e) {
          if (!this.mouse.started) {
            return false;
          }
          this.mouse.started = false;
          this.accurateChoiceFlag = false;
          let endX = e.offsetX;
          let endY = e.offsetY;
          // 马赛克遮罩
          let { beginX, beginY } = this.makeGridObject;
          var mouse = this.canvas.getPointer(e.e);
          var w = Math.abs(mouse.x - beginX),
            h = Math.abs(mouse.y - beginY);
          let obj = {
            beginX,
            beginY,
            w,
            h,
          };
          this.makeList.push(obj);
          this.drawMake();
        },
        pageTransformedIntoCanvas(callback) {
          let that = this;
          html2canvas(document.getElementById("pdf"), {
            allowTaint: true,
            useCORS: true,
          }).then(function (canvas) {
            let contentWidth = canvas.width;
            let contentHeight = canvas.height;
            //竖向打印
            let imgWidth = 595.28;
            let imgHeight = (592.28 / contentWidth) * contentHeight;
            let pageHeight = (contentWidth / 592.28) * 841.89;
            let leftHeight = contentHeight;
            let position = 0;
            if (contentWidth > contentHeight) {
              //横向打印
              imgWidth = 841.89;
              imgHeight = (841.89 / contentWidth) * contentHeight;
            }
            let pageData = canvas.toDataURL("image/jpeg", 1.0);
            let PDF;
            if (contentWidth <= contentHeight) {
              //竖向打印
              PDF = new jsPDF("", "pt", "a4");
            } else {
              //    横向打印
              PDF = new jsPDF("l", "pt", "a4");
            }
            if (leftHeight < pageHeight) {
              PDF.addImage(pageData, "JPEG", 0, 0, imgWidth, imgHeight);
            } else {
              while (leftHeight > 0) {
                PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight);
                leftHeight -= pageHeight;
                position -= 841.89;
                if (leftHeight > 0) {
                  PDF.addPage();
                }
              }
            }
            let datauri = PDF.output("dataurlstring");
            let base64 = datauri.split("base64,")[1];
            callback(pageData, PDF);
          });
        },
        drawMake() {
          if(!this.canvas)return;
          this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
          this.context.drawImage(this.bgImage, 0, 0);
          this.context.save();
          // if (this.canvas) this.canvas.clear();
          this.makeList.forEach((item) => {
            let { beginX, beginY, w, h } = item;
            this.makeGrid(beginX, beginY, w, h);
          });
        },
        clearClean() {
          if (this.canvas) this.canvas.clear();
        },
        printPdf() {
          let base64 = "";
          let datauri = "";
          let that = this;
          this.pageTransformedIntoCanvas((pageData, PDF) => {
            let blob = PDF.output("blob");
            that.print(blob);
          });
        },
        print(blob) {
          var date = new Date().getTime();
          var ifr = document.createElement("iframe");
          ifr.style.frameborder = "no";
          ifr.style.display = "none";
          ifr.style.pageBreakBefore = "always";
          ifr.setAttribute("id", "printPdf" + date);
          ifr.setAttribute("name", "printPdf" + date);
          ifr.src = window.URL.createObjectURL(blob);
          document.body.appendChild(ifr);
          this.doPrint("printPdf" + date);
          window.URL.revokeObjectURL(ifr.src); // 释放URL 对象
        },
        doPrint(val) {
          var ordonnance = document.getElementById(val).contentWindow;
          setTimeout(() => {
            ordonnance.print();
          }, 100);
        },
        makeGrid(beginX, beginY, rectWidth, rectHight) {
          const row = Math.round(rectWidth / this.squareEdgeLength) + 1;
          const column = Math.round(rectHight / this.squareEdgeLength) + 1;
          for (let i = 0; i < row * column; i++) {
            let x = (i % row) * this.squareEdgeLength + beginX;
            let y = parseInt(i / row) * this.squareEdgeLength + beginY;
            this.setColor(x, y);
          }
        },
        setColor(x, y) {
          const imgData = this.context.getImageData(
            x,
            y,
            this.squareEdgeLength,
            this.squareEdgeLength
          ).data;
          let r = 0,
            g = 0,
            b = 0;
          for (let i = 0; i < imgData.length; i += 4) {
            r += imgData[i];
            g += imgData[i + 1];
            b += imgData[i + 2];
          }
          r = Math.round(r / (imgData.length / 4));
          g = Math.round(g / (imgData.length / 4));
          b = Math.round(b / (imgData.length / 4));
          this.drawRect(
            x,
            y,
            this.squareEdgeLength,
            this.squareEdgeLength,
            `rgb(${r}, ${g}, ${b})`,
            2,
            `rgb(${r}, ${g}, ${b})`
          );
        },
        drawRect(
          x,
          y,
          width,
          height,
          fillStyle,
          lineWidth,
          strokeStyle,
          globalAlpha
        ) {
          this.context.beginPath();
          this.context.rect(x, y, width, height);
          this.context.lineWidth = lineWidth;
          if (this.fillColor) {
            fillStyle = this.fillColor;
            strokeStyle = this.fillColor;
          }
          this.context.strokeStyle = strokeStyle;
          fillStyle && (this.context.fillStyle = fillStyle);
          globalAlpha && (this.context.globalAlpha = globalAlpha);
          this.context.fill();
          this.context.stroke();
        },
      },
    };
    </script>
    <style lang="scss" scoped>
    .btn-list {
      & > div {
        margin-right: 10px;
      }
      & > div:last-child {
        margin-right: 0;
      }
    }
    .page-btn {
      text-align: center;
      display: flex;
      align-items: center;
      .page-count {
        min-width: 50px;
        margin: 0 10px;
      }
    }
    .pdf-box {
      position: relative;
    }
    .manager_detail {
      position: absolute;
      top: 0;
      left: 0;
    }
    .web-file {
      width: 65%;
      min-width: 900px;
      margin: 0 auto;
      .pdf-box {
        width: 100%; // height: 55vh;
        overflow: hidden;
      }
    }
    .accurate-choice {
      cursor: crosshair;
    }
    </style>
    

    起源地下载网 » fabric.js实现可视化签章以及遮罩打印的功能

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元