vue项目中使用的移动端的签名组件,纯 js 写的
<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>