首页 > 其他分享 >vue项目中使用的移动端的签名组件,纯 js 写的

vue项目中使用的移动端的签名组件,纯 js 写的

时间:2023-12-22 15:27:10浏览次数:40  
标签:canvas vue const ctx height width 组件 js config

<template>
  <section>
    <div class="sign-wrap">
      <div class="main">
        <div class="box" style="width: 100%;height: 100%">
          <!-- <vue-esign ref="esign" :width="600"  :height='1375' :isCrop="isCrop" :lineWidth="lineWidth" :lineColor="lineColor" :bgColor.sync="bgColor" /> -->
          <div class="drawing-board">
            <canvas id="canvas" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
          </div>
        </div>

      </div>
      <div class="dialog-header">签名确认</div>
      <div class="dialog-footer btns">
        <div class="btn confirm-btn  confirm-btn1" @click="reset">重置</div>
        <div class="btn confirm-btn " @click="save">确认</div>
      </div>
    </div>
  </section>
</template>

<script>
  const $ = (name) => document.querySelector(name);
  // 配置内容
  const config = {
    width: 0, // 宽度
    height: 0, // 高度
    lineWidth: 5, // 线宽
    strokeStyle: '#000000', // 线条颜色
    lineCap: 'round', // 设置线条两端圆角
    lineJoin: 'round', // 线条交汇处圆角
  };

  // 偏移量
  const client = {
    offsetX: 0,
    offsetY: 0,
  };

  let canvas;
  let ctx;

  //  import { XButton } from "vux";
  export default {
    //  components: { XButton },
    data() {
      return {
        lineWidth: 6,
        lineColor: "#000000",
        bgColor: "#ccc",
        resultImg: "", //base64结果数据
        isCrop: true, //是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
      };
    },
    mounted() {
      setTimeout(() => {
        if($('.drawing-board')){
          this.drawingBoardInit()
        }
      }, 600)

    },


    methods: {
      drawingBoardInit() {

        const {
          width,
          height,
          left,
          top,
        } = $('.drawing-board').getBoundingClientRect();
        config.width = width;
        config.height = height;
        client.offsetX = left;
        client.offsetY = top;
        // canvas 实例
        canvas = $('#canvas');
        // 设置宽高
        canvas.width = config.width;
        canvas.height = config.height;
        // 设置边框
        //   canvas.style.border = '1px solid #000';
        // 创建上下文
        ctx = canvas.getContext('2d');
        // 设置填充背景色
        ctx.fillStyle = 'transparent';
        // 绘制填充矩形
        ctx.fillRect(
          0, // x 轴起始绘制位置
          0, // y 轴起始绘制位置
          config.width, // 宽度
          config.height, // 高度
        );
      },
      // 鼠标按下
      touchStart(event) {
        event.preventDefault();
        // 获取偏移量及坐标
        const {
          clientX,
          clientY
        } = event.changedTouches[0];
        // 清除以上一次 beginPath 之后的所有路径,进行绘制
        ctx.beginPath();
        // 根据配置文件设置相应配置
        ctx.lineWidth = config.lineWidth;
        ctx.strokeStyle = config.strokeStyle;
        ctx.lineCap = config.lineCap;
        ctx.lineJoin = config.lineJoin;
        // 设置画线起始点位(减去 左边、上方的偏移量很关键)
        ctx.moveTo(clientX - client.offsetX, clientY - client.offsetY);
      },
      // 绘制
      touchMove(event) {

        // 获取当前坐标点位;
        const {
          clientX,
          clientY
        } = event.changedTouches[0];
        // 根据坐标点位移动添加线条(减去 左边、上方的偏移量很关键)
        ctx.lineTo(clientX - client.offsetX, clientY - client.offsetY);
        // 绘制
        ctx.stroke();
      },
      // 结束绘制
      touchEnd() {

        // 结束绘制
        ctx.closePath();
        // 移除鼠标移动或手势移动监听器
        window.removeEventListener('mousemove', this.draw);
      },
      // 清除
      reset() {
        // 清空当前画布上的所有绘制内容
        ctx.clearRect(0, 0, config.width, config.height);
      },
      // 将画布内容保存为图片
      save() {
        return new Promise((resolve, reject) => {
          if (!this.isCanvasBlank(canvas)) {
            this.rotateBase64(canvas.toDataURL('image/png')).then((img) => {
              const imgBase64 = img;
              // console.log(imgBase64, 'imgBase64-->>'); // base64编码
              this.$emit("handleImg", imgBase64);
            });
          } else {
            const err = '请签名';
            reject(err);
          }
        });
        // 将canvas上的内容转成blob流
        //   canvas.toBlob((blob) => {
        //     console.log(blob, 'blob-->>'); // 文件二进制流
        //     // 获取当前时间,用来当做文件名
        //     const date = new Date().getTime();
        //     // 创建一个 a 标签
        //     const link = document.createElement('a');
        //     // 设置 a 标签的下载文件名
        //     link.download = `${date}.png`;
        //     // 设置 a 标签的跳转路径为 文件流地址
        //     link.href = URL.createObjectURL(blob);
        //     // 手动触发 a 标签的点击事件
        //     link.click();
        //     // 移除 a 标签
        //     link.remove();
        //   });
      },
      // 判断canvas对象是否空
      isCanvasBlank(canvas) {
        const blank = document.createElement('canvas'); // 系统获取一个空canvas对象
        blank.width = config.width;
        blank.height = config.height;
        return canvas.toDataURL() === blank.toDataURL(); // 比较值相等则为空
      },
      // 将base64图片旋转90度以后上传
      rotateBase64(imgBase64) {
        return new Promise((resolve) => {
          const imgView = new Image();
          imgView.src = imgBase64;
          const canvas = document.createElement('canvas');
          const context = canvas.getContext('2d');
          const cutCoor = {
            sx: 0,
            sy: 0,
            ex: 0,
            ey: 0,
          };
          // 裁剪坐标
          imgView.onload = () => {
            const imgW = imgView.width;
            const imgH = imgView.height;
            const size = imgH;
            //   常量大小 = imgH;
            canvas.width = size * 2;
            canvas.height = size * 2;
            cutCoor.sx = size;
            cutCoor.sy = size - imgW;
            cutCoor.ex = size + imgH;
            cutCoor.ey = size + imgW;
            context.translate(size, size);
            context.rotate((Math.PI / 2) * 3);
            context.drawImage(imgView, 0, 0);
            const imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
            canvas.width = imgH;
            canvas.height = imgW;
            context.putImageData(imgData, 0, 0);
            resolve(canvas.toDataURL('image/png'));
          };
        });
      },
      // // 初始化方法
      // init() {
      //   this.$nextTick(() => {
      //     this.$refs.esign.reset();
      //   });
      // },
      // // 清空画板
      // handleReset() {
      //   this.$refs.esign.reset();
      // },
      // // 生成照片
      // handleGenerate() {
      //   // 生成图片
      //   // 可选配置参数 ,在未设置format或quality属性时可在生成图片时配置 例如: {format:'image/jpeg', quality: 0.5}
      //   // this.$refs.esign.generate({format:'image/jpeg', quality: 0.5})
      //   this.$refs.esign.generate().then((base64) => {
      //     this.resultImg = base64; //默认生成的是base64形式的图片
      //     // 将生成的base64格式的图片传给父组件
      //     this.$emit("handleImg", base64);
      //     //   如果需要下载
      //     //   const a = document.createElement("a");
      //     //   a.href = res;
      //     //   a.download = "签名.png";
      //     //   a.click();
      //     //   a.remove();
      //   })
      //     .catch((err) => {
      //       this.toast_warn(err); // 画布没有签字时会执行这里 'Not Signned'
      //     });
      // },
    },
  };
</script>

<style lang="less">
  section {
    /* height: calc(100% - 44px) */
    height:100%;
  }

  .sign-wrap {
    height: 100%;
    position: relative;

    .main {
      background-color: #ffffff;
      padding: 20px 50px 20px 77px;
      height: 100%;
    }

    .box {
      margin: 0 auto;
      background: #ecf0fa;
      border-radius: 14px;
      width: 100%;
      height: 100%;
      overflow: hidden;
    }

    .dialog-footer {
      display: flex;
      align-items: center;
      justify-content: space-around;
      width: 100%;

      .btn {
        font-size: 16px;
        margin-left: 14px;
        color: #666;
        padding: 14px 34px;
        text-align: center;
        box-sizing: border-box;
        background-color: #f8f8f8;
        border-radius: 4px;
        border-radius: 12px;
      }

      .confirm-btn {
        background-color: #117af1;
        color: #fff;
      }

      .confirm-btn1 {
        background-color: #fff;
        border: 1px solid #117af1;
        color: #117af1;
      }
    }
  }

  .dialog-header {
    display: -webkit-box;
    display: -ms-flexbox;
    display: flex;
    -webkit-box-align: center;
    -ms-flex-align: center;
    align-items: center;
    -ms-flex-pack: distribute;
    justify-content: space-around;
    width: 100%;
    position: absolute;
    -webkit-transform: rotate(90deg);
    top: 9%;
    transform: rotate(90deg);
    right: -3%;
    font-size: 19px;
    width: auto !important;
    font-weight: bold;
  }

  .btns {
    position: absolute;
    -webkit-transform: rotate(90deg);
    bottom: 18%;
    transform: rotate(90deg);
    left: -20%;
    width: auto !important;
  }

  .drawing-board {
    width: 100%;
    height: calc(100%);
    /* border-bottom: 1px solid #ccc; */
    box-sizing: border-box;
  }

  .tool-bar {
    width: 100%;
    height: 40px;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    position: absolute;
    top: 46%;
    left: -4rem;
    transform: rotate(90deg);

    .van-button {
      flex: 1
    }
  }
</style>

 

标签:canvas,vue,const,ctx,height,width,组件,js,config
From: https://www.cnblogs.com/smile-fanyin/p/17921638.html

相关文章

  • vue中使用Vue.extend方法仿写一个loading加载中效果
    需求描述本文我们使用vue的extend方法实现一个全屏loading加载效果,需求如下:通过命令就可以让弹框开启或关闭,比如this.$showDialog()开启,this.$hideDialog()关闭方法可以传参更改loading中的文字也可以传参更改loading背景色当然这里除了文字,背景色什么的,也可以传递更多的参......
  • k8s组件、工作原理详解
    1.k8s组件  Master组件:kube-apiserver(APIServer):角色:提供集群的唯一入口,处理所有API请求。原理:接收来自客户端(kubectl、UI界面)和其他组件的请求,验证和授权请求,然后将其转发到其他组件或更新etcd中的数据。etcd:角色:分布式键值存储,保存整个集群的状......
  • js 可选链操作符
    参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Optional_chaining#try_it可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(......
  • Js 之treeTable树状表格
    一、下载/**树形表格3.xCreatedbywangfanon2020-05-12https://gitee.com/whvse/treetable-lay*/layui.define(['laytpl','form','util'],function(exports){var$=layui.jquery;varlaytpl=layui.laytpl;varform......
  • Vue中的$refs 用法
    Vue中的$refs原理涉及到Vue实例的生命周期、虚拟DOM以及一些底层的JavaScript原理。Vue实例的生命周期:当Vue实例被创建时,它会经历一系列的生命周期钩子,包括beforeCreate、created、beforeMount、mounted等。在mounted生命周期钩子中,Vue实例的模板已经渲染到真实的DOM上,此时$refs会......
  • Vue中的$refs 用法
    Vue中的$refs用法Vue中的$refs原理涉及到Vue实例的生命周期、虚拟DOM以及一些底层的JavaScript原理。Vue实例的生命周期:当Vue实例被创建时,它会经历一系列的生命周期钩子,包括beforeCreate、created、beforeMount、mounted等。在mounted生命周期钩子中,Vue实例的模板已经渲染到......
  • [VUE] WebPack 打包后自动修改 dist 中 package.json 版本号
    我们在开发npm包时,开发期的package.json通常并不一定是发布到npm仓库的package.json。这种情况下每次改版本号需要改两个地方,比较麻烦。我一般使用webpack进行打包,所以有了下面这个小插件。插件源码modify.version.plugin.js/**修改版本号webpack插件*/functi......
  • Spring MVC 源码分析 - HandlerMapping 组件(二)之 HandlerInterceptor 拦截器
    HandlerMapping组件HandlerMapping组件,请求的处理器匹配器,负责为请求找到合适的 HandlerExecutionChain 处理器执行链,包含处理器(handler)和拦截器们(interceptors)handler 处理器是Object类型,可以将其理解成HandlerMethod对象(例如我们使用最多的 @RequestMapping 注解所标......
  • 巧妙使用Vue.extend继承组件实现el-table双击可编辑(不使用v-if和v-else)
    问题描述有一个简单的表格,产品要求实现双击可编辑看了一下网上的帖子,大多数都是搞两部分dom,一块是输入框,用于编辑状态填写;另一块是普通标签,用于在不编辑显示状态下呈现单元格文字内容。再加上一个flag标识搭配v-if和v-else去控制编辑状态、还是显示状态。大致代码如下:<el-t......
  • 爬虫及js相关部分内容
    爬虫websocket直播弹幕抓取逆向分析流程总结websocket,flashchrome插件添加了有道生词本的chromegoogle翻译扩展和有道翻译扩展js提取authtaobao账号authweb自动化新浪账号自动化刷新headlesspuppeteer抓取微指数nodejs后端sequelizesequelize应用hook实现对分......