使用vue3.0实现一个移动端电子签名组件

因业务需求,前段时间写了一个电子签名组件,在这里记录一下,绘图需求,首先肯定需要使用

canvas标签,考虑到在移动端使用,所以选择使用touch事件。

首先奉上html结构(该组件为横屏展示):

<div class="signName" :style="{top:0,left:differ+'px'}">
    <div class="close" @click="close"><img src="../assets/images/close.png" alt=""></div>
   <div class="autographBox">
      <div ref="canvasHW">
       <canvas
        @touchstart="touchStart($event)"
        @touchmove="touchMove($event)"
        @touchend="touchEnd($event)"
        ref="canvasF"
      ></canvas>
      </div>
      <p v-if="!isDraw">请本人签名</p>
   </div>
    <div class="autographBtn">
      <div @click="overwrite">重签</div>
      <div @click="seaveImages">确定</div>
    </div>
  </div>

css样式:

.signName{
  position: fixed;
  height: 100vw;
  width: 100vh;
  background-color: #fff;
  transform:rotate(90deg);
  -webkit-transform:rotate(90deg);
  transform-origin: left top;
  z-index: 1000;
  /* top:0;
  left: 0; */
}
.close{
  width: 100%;
  height: 10%;
  padding-left: 2.5rem;
  padding-top: 1.2rem;
}
.close img{
  width: 2rem;
}
 .autographBox{
  width: 100%;
  height: 80%;
  position: relative;
} 
.autographBox div{
  width: 100%;
  height: 100%;
}
.autographBox canvas{
  width: 100%;
  height: 100%;
}
.signName p{
  position: absolute;
  top:50%;
  left: 50%;
  transform: translate(-50%,-50%);
  font-size: 4rem;
  font-weight: bolder;
  color:#436CDF;
  opacity: 0.1;
} 
.autographBtn{
  width: 100%;
  height: 10%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.autographBtn div{
  width: 50%;
  height: 100%;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.3rem;
}
.autographBtn div:first-child{
  opacity: 0.4;
  background:  -webkit-linear-gradient(top, #728CFD 0%,#5C7EFE 100%);
}
.autographBtn div:last-child{
  background:  -webkit-linear-gradient(top, #728CFD 0%,#5C7EFE 100%);
}

其次定义变量,初始化canvas:

   var differ = ref(document.documentElement.clientWidth)
   var canvasF = ref(null);
   var canvasHW = ref(null);
   var canvasTxt = null; //画布
   var points = []; // 记录点
   var isDraw = ref(false); //签名标记
   var startX = 0; //开始坐标x
   var startY = 0;//开始坐标y
   var moveY= 0;
   var moveX= 0;
   var strDataURI ='' // 保存的canvas图像
    onMounted(() =>{
      let canvas =canvasF.value;
      canvas.height = canvasHW.value.offsetHeight - 10;
      canvas.width = canvasHW.value.offsetWidth - 10;
      canvasTxt = canvas.getContext("2d");
      canvasTxt.strokeStyle = '#333';
      canvasTxt.lineWidth = '3';
    }) 

主要事件方法如下:

  1. touchstart

    const touchStart = (ev) => {
          ev = ev || event;
          ev.preventDefault();
          if (ev.touches.length == 1) {
            isDraw.value = true; //签名标记
            let obj = {
              x: ev.targetTouches[0].clientY,
              y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
            }; //y的计算值中:document.body.offsetHeight*0.5代表的是除了整个画板signatureBox剩余的高,this.$refs.canvasHW.offsetHeight*0.1是画板中标题的高
            startX = obj.x;
            startY = obj.y;
            canvasTxt.beginPath();//开始作画
            points.push(obj);//记录点
          }
        }

  2. touchmove

    const touchMove = (ev)=> {
          ev = ev || event;
          ev.preventDefault();
          if (ev.touches.length == 1) {
            let obj = {
              x: ev.targetTouches[0].clientY,
              y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
            };
            moveY = obj.y;
            moveX = obj.x;
            canvasTxt.moveTo(startX, startY);//移动画笔
            canvasTxt.lineTo(obj.x, obj.y);//创建线条
            canvasTxt.stroke();//画线
            startY = obj.y;
            startX = obj.x;
            points.push(obj);//记录点
          }
        }

  3. touchend

    const touchEnd = (ev)=> {
          ev = ev || event;
          ev.preventDefault();
          if (ev.touches.length == 1) {
            let obj = {
              x: ev.targetTouches[0].clientY,
              y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
            };
            points.push(obj);//记录点
            points.push({ x: -1, y: -1 });//记录点
            canvasTxt.closePath();//收笔
            canvasTxt.fill();
          }
        }

  4. 重写

    const overwrite = ()=> {
          canvasTxt.clearRect(
            0,
            0,
            canvasF.value.width,
            canvasF.value.height
          );
          points = [];
          isDraw.value = false; //签名标记
        }

  5. 保存图片

    function seaveImages() {
        if(isDraw.value){
           strDataURI = canvasF.value.toDataURL("image/png");
           console.log(strDataURI)
           cxt.emit("autographConfirm", {
            baseCode:strDataURI,
          });
        }else{
          console.log(Toast);
          Toast('您还没有签名')
        }
      }

最后附上完整代码:

<template>
  <div class="signName" :style="{top:0,left:differ+'px'}">
    <div class="close" @click="close"><img src="../assets/images/close.png" alt=""></div>
   <div class="autographBox">
      <div ref="canvasHW">
       <canvas
        @touchstart="touchStart($event)"
        @touchmove="touchMove($event)"
        @touchend="touchEnd($event)"
        ref="canvasF"
      ></canvas>
      </div>
      <p v-if="!isDraw">请本人签名</p>
   </div>
    <div class="autographBtn">
      <div @click="overwrite">重签</div>
      <div @click="seaveImages">确定</div>
    </div>
  </div>
</template>
<script>
import { ref, onMounted } from "vue";
import { Toast } from "vant";
export default {
  name: "index",
  setup(props,cxt) {
    var differ = ref(document.documentElement.clientWidth)
   var canvasF = ref(null);
   var canvasHW = ref(null);
   var canvasTxt = null; //画布
   var points = []; // 记录点
   var isDraw = ref(false); //签名标记
   var startX = 0; //开始坐标x
   var startY = 0;//开始坐标y
   var moveY= 0;
   var moveX= 0;
   var strDataURI ='' // 保存的canvas图像
    onMounted(() =>{
      let canvas =canvasF.value;
      canvas.height = canvasHW.value.offsetHeight - 10;
      canvas.width = canvasHW.value.offsetWidth - 10;
      canvasTxt = canvas.getContext("2d");
      canvasTxt.strokeStyle = '#333';
      canvasTxt.lineWidth = '3';
    }) 
    const touchStart = (ev) => {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        isDraw.value = true; //签名标记
        let obj = {
          x: ev.targetTouches[0].clientY,
          y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
        }; //y的计算值中:document.body.offsetHeight*0.5代表的是除了整个画板signatureBox剩余的高,this.$refs.canvasHW.offsetHeight*0.1是画板中标题的高
        startX = obj.x;
        startY = obj.y;
        canvasTxt.beginPath();//开始作画
        points.push(obj);//记录点
      }
    }
   const touchMove = (ev)=> {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        let obj = {
          x: ev.targetTouches[0].clientY,
          y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
        };
        moveY = obj.y;
        moveX = obj.x;
        canvasTxt.moveTo(startX, startY);//移动画笔
        canvasTxt.lineTo(obj.x, obj.y);//创建线条
        canvasTxt.stroke();//画线
        startY = obj.y;
        startX = obj.x;
        points.push(obj);//记录点
      }
    }
    const touchEnd = (ev)=> {
      ev = ev || event;
      ev.preventDefault();
      if (ev.touches.length == 1) {
        let obj = {
          x: ev.targetTouches[0].clientY,
          y: differ.value- ev.targetTouches[0].clientX - (differ.value*0.1)
        };
        points.push(obj);//记录点
        points.push({ x: -1, y: -1 });//记录点
        canvasTxt.closePath();//收笔
        canvasTxt.fill();
      }
    }
    const overwrite = ()=> {
      canvasTxt.clearRect(
        0,
        0,
        canvasF.value.width,
        canvasF.value.height
      );
      points = [];
      isDraw.value = false; //签名标记
    }
  function seaveImages() {
    if(isDraw.value){
       strDataURI = canvasF.value.toDataURL("image/png");
       console.log(strDataURI)
       cxt.emit("autographConfirm", {
        baseCode:strDataURI,
      });
    }else{
      console.log(Toast);
      Toast('您还没有签名')
    }
  }
  function close(){
    cxt.emit("close", {
      closeFlag:false,
    });
  }
    return {
      differ,
      canvasF,
      canvasHW,
      isDraw,
      touchStart,
      touchMove,
      touchEnd,
      overwrite,
      seaveImages,
      close
    };
  },
};
</script>
<style scoped>
.signName{
  position: fixed;
  height: 100vw;
  width: 100vh;
  background-color: #fff;
  transform:rotate(90deg);
  -webkit-transform:rotate(90deg);
  transform-origin: left top;
  z-index: 1000;
  /* top:0;
  left: 0; */
}
.close{
  width: 100%;
  height: 10%;
  padding-left: 2.5rem;
  padding-top: 1.2rem;
}
.close img{
  width: 2rem;
}
 .autographBox{
  width: 100%;
  height: 80%;
  position: relative;
} 
.autographBox div{
  width: 100%;
  height: 100%;
}
.autographBox canvas{
  width: 100%;
  height: 100%;
}
.signName p{
  position: absolute;
  top:50%;
  left: 50%;
  transform: translate(-50%,-50%);
  font-size: 4rem;
  font-weight: bolder;
  color:#436CDF;
  opacity: 0.1;
} 
.autographBtn{
  width: 100%;
  height: 10%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.autographBtn div{
  width: 50%;
  height: 100%;
  color: #fff;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 1.3rem;
}
.autographBtn div:first-child{
  opacity: 0.4;
  background:  -webkit-linear-gradient(top, #728CFD 0%,#5C7EFE 100%);
}
.autographBtn div:last-child{
  background:  -webkit-linear-gradient(top, #728CFD 0%,#5C7EFE 100%);
}
</style>

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的 Vue3.0 Todolist 示例代码: ```html <template> <div> <h1>Todolist</h1> <form @submit.prevent="addTodo"> <input type="text" v-model="newTodo" placeholder="Add a new todo"> <button>Add</button> </form> <ul> <li v-for="(todo, index) in todos" :key="index"> <input type="checkbox" v-model="todo.completed" @change="toggleTodoStatus(index)"> <span :class="{completed: todo.completed}">{{ todo.text }}</span> <button @click="removeTodo(index)">X</button> </li> </ul> </div> </template> <script> import { reactive } from 'vue'; export default { setup() { const state = reactive({ newTodo: '', todos: [], }); const addTodo = () => { if (state.newTodo.trim() !== '') { state.todos.push({ text: state.newTodo, completed: false }); state.newTodo = ''; } }; const toggleTodoStatus = (index) => { state.todos[index].completed = !state.todos[index].completed; }; const removeTodo = (index) => { state.todos.splice(index, 1); }; return { newTodo: state.newTodo, todos: state.todos, addTodo, toggleTodoStatus, removeTodo, }; }, }; </script> <style> .completed { text-decoration: line-through; } </style> ``` 这是一个基本的 Todolist 组件使用Vue3.0 的 Composition API 和响应式数据。在模板中,使用了 v-model 指令来双向绑定输入框和数据,使用 v-for 指令来循环渲染列表项。在 setup 函数中,使用了 reactive 函数来创建响应式数据对象,使用了箭头函数来定义组件的方法。在样式中,使用了伪类选择器 :class 来动态添加类名,实现对已完成的任务的样式修改。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值