原理:signature_pad插件,canvas
<template> <div id="app"> <button @click="handleClick">点击签名</button> <img :src="imgUrl" v-if="showImg" style="border:1px dashed #ccc;"/> <div class="sign-box" v-show="showSignature"> <div class="bgc"></div> <div class="main-box"> <span @click="cancelBgc" class="cancel">取消</span> <p class="title">签名</p> <p class="sub-title">请用正楷签下您的名字</p> <div :style="{w,height:h}" class="canvass"> <canvas :id="uid" class="canvas" :data-uid="uid"></canvas> </div> <div id='reset' @click="clear"> <span>清除</span> </div> <div @click="saveSign" class="next">确认</div> </div> </div> </div> </template> <script> /* eslint-disable */ import SignaturePad from 'signature_pad'; export default { data() { return { showSignature: false, showImg:false, sig: () => { }, uid: 'canvas', option: { backgroundColor: 'rgb(255,255,255)', penColor: 'rgb(0, 0, 0)', minWidth: 0.8, maxWidth: 4 }, w: '94.6%', h: "240px", } }, methods: { handleClick() { this.showSignature = true; this.$nextTick(() => { this.draw(); }) }, // 签名 draw() { var that = this; var canvas = document.getElementById(that.uid); that.sig = new SignaturePad(canvas, that.option); window.addEventListener('resize', that.resizeCanvas); that.resizeCanvas(); }, resizeCanvas() { var that = this var canvas = document.getElementById(that.uid); var url; var ratio = Math.max(window.devicePixelRatio || 1, 1); canvas.width = canvas.offsetWidth * ratio; canvas.height = canvas.offsetHeight * ratio; canvas.getContext('2d').scale(ratio, ratio); that.clear() !that.clearOnResize && url !== undefined && that.fromDataURL(url); }, fromDataURL(url) { var that = this; that.sig.fromDataURL(url); }, // 取消签名 cancelBgc() { this.showSignature = false; }, // 清除签名 clear() { var that = this; that.sig.clear(); }, isEmpty() { var that = this; return that.sig.isEmpty(); }, // 保存签名 saveSign() { var that = this; if (that.isEmpty()) { alert('请用正楷签下您的名字'); return } var sign = { signature: that.sig.toDataURL().split(',')[1] } that.imgUrl = "data:image/png;base64," + sign.signature; that.showImg = true; that.showSignature = false; } } } /* eslint-disable */ </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin-top: 60px; } .canvass { margin: auto; background: #fff; border: 1px dashed #e5e5e5; border-radius: 8px; margin-top: 17px; position: relative; } #reset { text-align: center; font-size: 14px; color: #666666; margin-top: 26px; margin-bottom: 47px; } #reset img { 13px; height: 13px; } canvas { 100%; height: 100%; } .sign-box { position: fixed; top: 0; left: 0; 100%; min-height: 100vh; } .bgc { opacity: 0.7; background: #000000; 100%; height: 100%; position: absolute; top: 0; z-index: 5; } .main-box { height: 500px; 100%; background-color: #fff; position: absolute; bottom: 0; z-index: 10; } .cancel { position: absolute; font-size: 18px; color: #31A4FF; left: 15px; top: 16px; } .title { text-align: center; font-size: 18px; color: #333333; padding-top: 16px; padding-bottom: 4px; } .sub-title { font-size: 12px; color: #999999; text-align: center; } .next { border: 1px solid #31A4FF; height: 44px; text-align: center; line-height: 44px; font-size: 18px; color: #31A4FF; background: #fff; margin: 0 15px; } .footer { position: fixed; left: 0; bottom: 0; display: flex; justify-content: space-between; 100%; } .pre { 28%; margin-right: 1px; border-top: 1px solid #d6d6d6; color: #454545; background: #fff; } .share { 44%; border-top: 1px solid #d6d6d6; background: #66b9ff; color: #fff; } .nextOne { 28%; color: #fff; background: #31A4FF; } .commoncss { height: calc(110rem / 24); line-height: calc(110rem / 24); font-size: 1.6rem; text-align: center; box-sizing: border-box; margin-top: 1rem; } .hasLinkpre { margin-top: 1rem; background: #fff; 50%; height: calc(110rem / 24); line-height: calc(110rem / 24); font-size: 1.6rem; text-align: center; box-sizing: border-box; color: #454545; } .hasLinknextone { margin-top: 1rem; background: #31A4FF; 50%; height: calc(110rem / 24); line-height: calc(110rem / 24); font-size: 1.6rem; text-align: center; box-sizing: border-box; color: #fff; } </style>
效果图:
横屏签名:
<template> <div id="app"> <div class="content"> <div class="btn" @click='toSign()'>点击此处签名</div> <img :src="'data:image/png;base64,'+appntSign" alt="" v-show="appntSign" id="signImg"> </div> <div id="signContent" class='signBox' v-show="showSign"> <div class="canvass"> <canvas :id="uid" class="canvas" :data-uid="uid"></canvas> </div> <div class='footer'> <span @click="clear()">重签</span> <span @click="keepSign()" :class='{active:canSaveBtn}'>保存签名</span> </div> </div> </div> </template> <script> /* eslint-disable */ import SignaturePad from 'signature_pad'; export default { props: { showSignPage: { type: Boolean, default: false }, relation: { type: String }, sigOption: { type: Object, default: () => { return { backgroundColor: 'rgb(255,255,255)', penColor: 'rgb(0, 0, 0)' } } }, w: { type: String, default: '90%' }, h: { type: String, default: '250px' }, clearOnResize: { type: Boolean, default: false } }, data() { return { sig: () => { }, option: { backgroundColor: 'rgb(255,255,255)', penColor: 'rgb(0, 0, 0)' }, uid: '', signPerson: '投保人', time: 0, contNo: '', num: '', getdata: "", buttomShow: false, showSign: false, appntSign: '', canSaveBtn: false, needRotate: ''// 0 不旋转 1旋转 } }, created() { var _this = this this.uid = 'canvas' + _this._uid var sigOptions = Object.keys(_this.sigOption) for (var item of sigOptions) { _this.option[item] = _this.sigOption[item] } }, watch: { // 险种信息监听 showSignPage: { handler: function (val, oldVal) { this.resizeCanvas() }, // 深度观察 deep: true } }, methods: { checkbox() { if (!this.appntSign) { this.buttomShow = false; Dialog.alert({ message: '请签名' }).then(() => { // on close }); return } this.buttomShow = !this.buttomShow; }, draw() { var _this = this var canvas = document.getElementById(_this.uid) _this.sig = new SignaturePad(canvas, _this.option) window.addEventListener('resize', _this.resizeCanvas) _this.resizeCanvas() canvas.addEventListener('touchstart', function (e) { e.preventDefault() _this.canSave() }) _this.setMark('请在此处使用正楷签名') }, resizeCanvas() { var _this = this var canvas = document.getElementById(_this.uid) var url var ratio = Math.max(window.devicePixelRatio || 1, 1) canvas.width = canvas.offsetWidth * ratio canvas.height = canvas.offsetHeight * ratio canvas.getContext('2d').scale(ratio, ratio) _this.clear() !_this.clearOnResize && url !== undefined && _this.fromDataURL(url) }, clear() { var _this = this _this.sig.clear() this.canSaveBtn = false }, canSave() { this.canSaveBtn = true }, toSign() { this.appntSign = '' this.showSign = true this.buttomShow = false; this.$nextTick(() => { this.draw() }) }, keepSign() { //保存签名 var _this = this if (_this.isEmpty()) { return } _this.appntSign = _this.sig.toDataURL().split(',')[1] _this.showSign = false clearInterval(this.time) let id = _this.setWatermark('请在此处使用正楷签名'); let rotate = document.getElementById(id).style.transform console.log(rotate) document.getElementById(id).style.display = 'none' let img = document.getElementById('signImg') debugger if (!rotate) { img.style.height = '65px' img.style.width = '' img.style.marginBottom = '-20px' img.style.marginLeft = '15px' img.style.transform = '' _this.needRotate = '0' } else { img.style.width = '65px' img.style.height = '' img.style.marginBottom = '-50px' img.style.marginLeft = '35px' img.style.transform = 'rotate(-90deg)' _this.needRotate = '1' } }, cancel() { this.$emit('callback') }, save(format) { var _this = this var str = ""; if (!_this.appntSign) { Dialog.alert({ message: '请签名' }).then(() => { // on close }); return } let params = { contNo: _this.contNo, status: "02", imgBase64: _this.appntSign, index: "0", faceImageFlag: sessionStorage.getItem("faceImageFlag"), needRotate: _this.needRotate } questionnaireSubmit(params).then(result => { if (result && result.errorCode === "1") { _this.getdata = result.responseBody; str = _this.getdata.appntName + "," + _this.getdata.contNo + "," + _this.getdata.mainRiskName + "," + _this.getdata.addRiskName + "," + _this.getdata.statusLabel + "," + _this.getdata.visitDate + "," + _this.getdata.visitStatus + "," + _this.num; sessionStorage.setItem("camera", true); //true (正在进行中) false (下一次重新开始) _this.$router.push({ path: "end", query: { postdata: str } }) } else { _this.$toast(result.errorMessage) } }) .catch(rej => { console.log("签名", rej) }) _this.buttomShow = !_this.buttomShow }, fromDataURL(url) { var _this = this _this.sig.fromDataURL(url) }, isEmpty() { var _this = this return _this.sig.isEmpty() }, undo() { var _this = this var data = _this.sig.toData() if (data) { data.pop() _this.sig.fromData(data) } }, setWatermark(str) { // 水印背景设置 let id = '1.23452384164.123412416'; if (document.getElementById(id) !== null) { document.body.removeChild(document.getElementById(id)); } //创建一个画布 let can = document.createElement('canvas'); //设置画布的长宽 can.width = 500; can.height = 100; let cans = can.getContext('2d'); //旋转角度 // cans.rotate(-15 * Math.PI / 180); cans.font = '40px Vedana'; //设置填充绘画的颜色、渐变或者模式 cans.fillStyle = 'rgba(200, 200, 200, 0.40)'; //设置文本内容的当前对齐方式 cans.textAlign = 'left'; //设置在绘制文本时使用的当前文本基线 cans.textBaseline = 'Middle'; //在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置) cans.fillText(str, can.width / 8, can.height / 2); let div = document.createElement('div'); div.id = id; div.style.pointerEvents = 'none'; div.style.position = 'fixed'; div.style.zIndex = '100000'; let e = document.getElementsByClassName('canvas')[0] let height = e.offsetHeight let width = e.offsetWidth console.log(width) console.log(height) if (width > height) { // 横屏 div.style.width = width + 'px'; div.style.height = height + 'px'; div.style.top = '20px'; div.style.left = '15px'; } else { // 竖屏 div.style.width = height + 'px'; div.style.height = width + 'px'; div.style.top = 20 + (height - width) / 2 + 'px'; div.style.right = 15 - (height - width) / 2 + 'px'; div.style.transform = 'rotate(90deg)' } div.style.background = 'url(' + can.toDataURL('image/png') + ') center center no-repeat'; document.body.appendChild(div); return id; }, setMark(str) { // 显示水印背景 let id = this.setWatermark(str); this.time = setInterval(() => { if (document.getElementById(id) === null) { id = this.setWatermark(str); } }, 500); window.onresize = () => { this.setWatermark(str); }; } }, mounted() { } } /* eslint-disable */ </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; margin-top: 60px; } .content { background: #fff; padding: 20px 23px 35px; min-height: 200px; } .content h4 { font-size: 16px; margin-bottom: 20px; color: #333; } .content p { color: #666; font-size: 14px; } .content div.btn { 150px; height: 40px; line-height: 40px; text-align: center; background: #31A4FF; color: #fff; font-size: 16px; margin: 15px auto 40px; } .content span { color: #666; font-size: 14px; } /* 竖屏情况下 */ @media screen and (orientation:portrait) { .signBox { position: absolute; top: 0; left: 0; bottom: 0; 100%; background: #fff; z-index: 10000; } .canvass { padding: 20px 15px; 100%; height: 90%; box-sizing: border-box; } canvas { 100%; height: 100%; border: 1px dashed #333; border-radius: 8px; } .footer { text-align: center; height: 10%; padding: 10px 0; writing-mode: vertical-lr; box-sizing: border-box; } .footer span { border: 1px solid #999; color: #999; height: 30vw; 40px; display: inline-block; line-height: 40px; font-size: 16px; text-align: center; background: #fff; margin-top: -40px; margin-left: 24vw; transform: rotate(90deg) } .footer span:last-child { background: #ccc; color: #fff; border-color: transparent; margin-left: 34vw; } .footer span.active { background: #31A4FF; color: #fff; } } /* 横屏情况下 */ @media screen and (orientation:landscape) { .signBox { position: absolute; top: 0; left: 0; bottom: 0; 100%; background: #fff; z-index: 10000; } .canvass { float: left; padding: 12px 10px; 90%; height: 100%; box-sizing: border-box; } canvas { 100%; height: 100%; border: 1px dashed #333; border-radius: 5px; } .footer { text-align: center; float: right; 10%; height: 100%; padding: 0 5px; box-sizing: border-box; writing-mode: vertical-lr; } .footer span { border: 1px solid #999; color: #999; 22px; height: 30vh; display: inline-block; line-height: 22px; font-size: 8px; text-align: center; background: #fff; margin-top: calc (13vh - 20px); } .footer span:last-child { background: #ccc; color: #fff; border-color: transparent; margin-top: 14vh; } .footer span.active { background: #31A4FF; color: #fff; } } .take-photo { margin: auto; } .take-photo #submit { background: #31A4FF; color: #fff; font-size: 16px; text-align: center; margin: 15px auto; 90%; height: 100%; line-height: 1.5; } .take-photo #submit2 { background: #E0E0E0; color: #fff; font-size: 16px; text-align: center; margin: 15px auto; 90%; 90%; height: 100%; line-height: 1.5; } .mobile-ts { color: #4c4c4c; font-size: 12px; padding: 10px 10px 0px 25px; position: relative; text-indent: 25px; } .imgSpan { position: absolute; left: 25px; text-indent: 0; } .mobile-ts .imgSpan img { 16px; height: 16px; } </style>