1.封装公共的组件 创建 components/SignCanvas.vue

<template><canvas:ref="domId"class="app-sign-canvas":id="domId"@mousedown.prevent.stop="handleMousedown"@mousemove.prevent.stop="handleMousemove"@mouseup.prevent.stop="handleMouseup"@mouseleave.prevent.stop="handleMouseleave"@touchstart.prevent.stop="handleTouchstart"@touchmove.prevent.stop="handleTouchmove"@touchend.prevent.stop="handleTouchend">您的浏览器不支持canvas技术,请升级浏览器!</canvas>
</template>
<script>
export default {name: 'SignCanvas',model: {value: 'image',event: 'confirm'},props: {image: {required: false,type: [String],default: null},options: {//配置项required: false,type: [Object],default: () => null}},data() {return {value: null, //base64domId: `sign-canvas-${Math.random().toString(36).substr(2)}`, //生成唯一dom标识canvas: null, //canvas dom对象context: null, //canvas 画布对象dpr: 1,config: {isFullScreen: false, //是否全屏手写 [Boolean] 可选isDpr: false, //是否使用dpr兼容高分屏 [Boolean] 可选lastWriteSpeed: 1, //书写速度 [Number] 可选lastWriteWidth: 2, //下笔的宽度 [Number] 可选lineCap: 'round', //线条的边缘类型 [butt]平直的边缘 [round]圆形线帽 [square]    正方形线帽lineJoin: 'round', //线条交汇时边角的类型  [bevel]创建斜角 [round]创建圆角 [miter]创建尖角。canvasWidth: 600, //canvas宽高 [Number] 可选canvasHeight: 600, //高度  [Number] 可选bgColor: '#ccc', //背景色 [String] 可选 注:这背景色仅仅只是canvas背景,保存的图片仍然是透明的writeWidth: 5, //基础轨迹宽度  [Number] 可选maxWriteWidth: 30, // 写字模式最大线宽  [Number] 可选minWriteWidth: 5, // 写字模式最小线宽  [Number] 可选writeColor: '#101010', // 轨迹颜色  [String] 可选isSign: false, //签名模式 [Boolean] 默认为非签名模式,有线框, 当设置为true的时候没有任何线框imgType: 'png', //下载的图片格式  [String] 可选为 jpeg  canvas本是透明背景的},resizeTimer: null}},mounted() {this.init()},watch: {options: {handler() {this.init()},deep: true}},methods: {init() {const options = this.optionsif (options) {for (const key in options) {this.config[key] = options[key]}}this.dpr =typeof window !== 'undefined' && this.config.isDpr? window.devicePixelRatio ||window.webkitDevicePixelRatio ||window.mozDevicePixelRatio ||1: 1this.canvas = document.getElementById(this.domId)if (!this.canvas) return  // 横竖屏旋转问题this.context = this.canvas.getContext('2d')this.canvas.style.background = this.config.bgColorthis.canvasInit()this.canvasClear()},/*** 轨迹宽度*/setLineWidth() {const nowTime = Date.now()const { isSign, writeWidth } = this.config//写字模式和签名模式if (isSign) {this.context.lineWidth = writeWidth * this.dprthis.config.lastWriteTime = nowTimereturn}const { lastWriteTime, minWriteWidth, maxWriteWidth } = this.configconst diff = nowTime - lastWriteTimethis.config.lastWriteTime = nowTimelet calcWidth =minWriteWidth + ((maxWriteWidth - minWriteWidth) * diff) / 30if (calcWidth < minWriteWidth) {calcWidth = minWriteWidth} else if (calcWidth > maxWriteWidth) {calcWidth = maxWriteWidth}calcWidth = calcWidth.toFixed(2)const { lastWriteWidth } = this.configconst lineWidth = (this.config.lastWriteWidth =(lastWriteWidth / 4) * 3 + calcWidth / 4)this.context.lineWidth = lineWidth * this.dpr},/*** 写开始*/writeBegin(point) {this.config.isWrite = truethis.config.lastWriteTime = Date.now()this.config.lastPoint = pointthis.writeContextStyle()},/*** 绘制轨迹*/writing(point) {this.context.beginPath()this.context.moveTo(this.config.lastPoint.x * this.dpr,this.config.lastPoint.y * this.dpr)this.context.lineTo(point.x * this.dpr, point.y * this.dpr)this.setLineWidth()this.context.stroke()this.config.lastPoint = pointthis.context.closePath()},/*** 写结束*/writeEnd(point) {this.config.isWrite = falsethis.config.lastPoint = pointthis.saveAsImg()},/*** 轨迹样式*/writeContextStyle() {this.context.beginPath()this.context.strokeStyle = this.config.writeColorthis.context.lineCap = this.config.lineCapthis.context.lineJoin = this.config.lineJoin},/*** 清空画板*/canvasClear() {this.context.save()this.context.strokeStyle = this.config.writeColorthis.context.clearRect(0, 0, this.canvas.width, this.canvas.height)this.context.fillStyle = this.config.bgColorif (this.config.isFullScreen) {this.context.fillRect(0, 0, this.canvas.height, this.canvas.width)} else {this.context.fillRect(0, 0, this.canvas.width, this.canvas.height)}this.config.isData = falsethis.$emit('confirm', null)this.context.restore()},/***  保存图片 格式base64*/saveAsImg() {const image = new Image()image.src = this.canvas.toDataURL()this.$emit('confirm', image.src)this.config.isData = truereturn image.src},/*** 初始化画板*/canvasInit() {const height = this.canvas.offsetHeight || this.config.canvasHeightconst width = this.canvas.offsetWidth || this.config.canvasWidththis.canvas.width = width * this.dprthis.canvas.height = height * this.dprthis.canvas.style.width = `${width}px`this.canvas.style.height = `${height}px`if (this.config.isFullScreen) {this.context.rotate((-90 * Math.PI) / 180)this.context.translate(-height, 0)}},/*** 鼠标按下 => 下笔*/handleMousedown(e) {this.writeBegin({ x: e.offsetX || e.clientX, y: e.offsetY || e.clientY })},/*** 书写过程 => 下笔书写*/handleMousemove(e) {this.config.isWrite && this.writing({ x: e.offsetX, y: e.offsetY })},/*** 鼠标松开 => 提笔*/handleMouseup(e) {this.writeEnd({ x: e.offsetX, y: e.offsetY })},/*** 离开书写区域 => 提笔离开*/handleMouseleave(e) {this.config.isWrite = falsethis.config.lastPoint = { x: e.offsetX, y: e.offsetY }},/* ==========================移动端兼容=Start================================ *//*** 手指按下 => 下笔*/handleTouchstart(e) {const touch = e.targetTouches[0]const point = this.calcPoint(touch)this.writeBegin(point)},/*** 手指移动 => 下笔书写*/handleTouchmove(e) {const touch = e.targetTouches[0]const point = this.calcPoint(touch)this.config.isWrite && this.writing(point)},/*** 手指移动结束 => 提笔离开*/handleTouchend(e) {const touch = (e.targetTouches || [])[0] || (e.changedTouches || [])[0]const point = this.calcPoint(touch)this.writeEnd(point)},/* ==========================移动端兼容=End================================== *//*** 下载二维码到本地*/downloadSignImg(name) {const c = document.getElementById(this.domId)const dataURL = c.toDataURL('image/png')this.saveFile(dataURL,name? `${name}.${this.config.imgType}`: `${Date.parse(new Date())}.${this.config.imgType}`)},/*** 保存文件*/saveFile(data, filename) {const saveLink = document.createElementNS('http://www.w3.org/1999/xhtml','a')saveLink.href = datasaveLink.download = filenameconst event = document.createEvent('MouseEvents')event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);saveLink.dispatchEvent(event);},calcPoint(touch) {const { left, top } = this.canvas.getBoundingClientRect()const offset = this.offset(touch.target)const { clientX, clientY, pageX, pageY } = touchconst x = clientX ? clientX - left : pageX - offset.leftconst y = clientY ? clientY - top : pageY - offset.topreturn { x, y }},/*** 获取dom对象的偏移量 可以获取解决position定位的问题* @returns number*/offset(target) {const { offsetTop, offsetLeft } = targetconst result = { top: offsetTop, left: offsetLeft }let parent = target.offsetParentwhile (parent) {result.top += parent.offsetTopresult.left += parent.offsetLeftparent = parent.offsetParent}return result},/*** 图片压缩*/dealImage() {//压缩系数0-1之间var quality = 0.6var canvas = document.createElement('canvas')var ctx = canvas.getContext('2d')var dealWidth = 300 //目标尺寸var dealHeight = 200canvas.width = dealWidthcanvas.width = dealHeight// if (Math.max(imgWidth, imgHeight) > w) {//     if (imgWidth > imgHeight) {//         canvas.width = w//         canvas.height = w * imgHeight / imgWidth//     } else {//         canvas.height = w//         canvas.width = w * imgWidth / imgHeight//     }// } else {//     canvas.width = imgWidth//     canvas.height = imgHeight//     quality = 0.6// }const c = document.getElementById(this.domId)const dataURL = c.toDataURL('image/png')ctx.clearRect(0, 0, canvas.width, canvas.height)ctx.drawImage(dataURL, 0, 0, canvas.width, canvas.height)var ba = canvas.toDataURL('image/jpeg', quality) //压缩语句console.log(ba)}}
}
</script>

2.封装电子签名弹窗 创建文件 SginWindow.vue 之html+js代码

<template><van-dialog class="sign-dialog" :class="[{'horiz': isHorizontal}, {'full': fullScreen}]" :value="show" :showConfirmButton="false"><div class="sign-layout" v-if="show"><div class="icon-close" @click="handleClose"><van-icon name="cross" color="#000" size="22" /></div><div class="tips">请在下方签名区域签字</div><div ref="SIGNBOX" class="sign-box" v-if="show"><SignCanvas class="c-canvas" v-if="showSignCanvas" v-model="signImgData" ref="SIGN" :options="options" /></div><div class="btns"><van-button class="btn btn-clear" :disabled="sbLoading" plain @click="handleClearCanvas">清除重写</van-button><!-- <van-button class="btn btn-clear" :disabled="sbLoading" plain @click="handleFinish">放弃自动续保直接个账支付</van-button> --><van-button class="btn btn-ok" type="primary" :loading="sbLoading" loading-text="提交中..." @click="handleSaveImage">提交签字</van-button></div></div></van-dialog>
</template><script>
import SignCanvas from '@/components/SignCanvas'
import { saveSignatureData } from '@/api/pay.js'
import { Toast } from 'vant'
export default {name: 'SginWindow',components: { SignCanvas },props: {show: Boolean,isRenewal: String},data() {return {sbLoading: false,showSignCanvas: false,signImgData: null,// isFullScreen: true,isHorizontal: false,options: {bgColor: '#f6f6f6',writeColor: '#000',isSign: true,writeWidth: 2,isFullScreen: true}}},mounted() {this.init()window.addEventListener('resize', this.renderResize)},destroyed() {window.removeEventListener('resize', this.renderResize)},computed: {fullScreen() {const { isHorizontal } = thisreturn isHorizontal ? false : true}},methods: {init() {this.setHorizontalState()this.resetCanvas()},resetCanvas() {this.signImgData = nullthis.showSignCanvas = falseconst showCanvasFun = () => {this.options.isFullScreen = this.fullScreenthis.$nextTick(() => {this.showSignCanvas = true})}setTimeout(showCanvasFun, 200)},handleClearCanvas() {this.showSignCanvas = falsethis.$nextTick(() => {this.showSignCanvas = true})this.$refs.SIGN?.canvasClear()},handleSaveImage() {if (!this.signImgData) {Toast('请签字后再提交')return }console.log(this.signImgData,'this.signImgDatathis.signImgData')const autographImgUrl = this.signImgData.split(',')[1]this.sbLoading = trueconst { isRenewal } = thisconst param = { isRenewal, autographImgUrl }saveSignatureData(param).then((res) => {console.log(res)this.sbLoading = falsethis.handleFinish()}).catch((e) => {console.log(e)this.sbLoading = false})},handleFinish() {this.$emit('ok')this.handleClose()},handleClose() {this.$emit('update:show', false)this.resetCanvas()},setHorizontalState() {const width = document.documentElement.clientWidthconst height = document.documentElement.clientHeightthis.isHorizontal = width > height},renderResize() {this.setHorizontalState()this.resetCanvas()}}
}
</script>

css

<style lang="less" scoped>
.sign-layout {padding: 10px;position: relative;display: flex;flex-direction: column;min-height: 300px;.icon-close {position: absolute;right: 15px;top: 10px;display: flex;align-items: center;justify-content: center;}.title {text-align: center;color: 333;padding-bottom: 5px;}.tips {text-align: center;color: #999;padding: 10px;flex-shrink: 0;}.sign-box {flex: 1;display: flex;// border: 1px solid #ccc;overflow: hidden;.c-canvas {flex: 1;}}.btns {display: flex;justify-content: space-around;align-items: center;padding-top: 15px;flex-shrink: 0;.btn {min-width: 20%;border-radius: 10px;height: 40px;}.btn-clear {border: 1px solid #f61f1e;color: #f61f1e;}.btn-ok {background: linear-gradient(180deg, #da2f0a 0%, #a80000 100%);border: none;}}
}.full {width: 100vw;height: 100vh;top: 0;left: 0;transform: initial;.sign-layout {transform: rotate(90deg)translate(calc((100vh - 100vw) / 2), calc((100vh - 100vw) / 2));transform-origin: center center;width: 100vh;height: 100vw;padding: 10px;box-sizing: border-box;}
}.horiz {width: 100vw;height: 100vh;top: 50%;.sign-layout {width: 100%;height: 100vh;padding: 2vh;min-height: initial;box-sizing: border-box;}.icon-close {top: 4vh;}.tips {padding: 3vh 0;box-sizing: border-box;// height: 8vh;box-sizing: border-box;}.btns {padding-top: 3vh;.btn {height: 14vh;}}
}
</style>

3.页面中使用电子签名

<template><SginWindow :show.sync="showSign" :isRenewal="isAutoYearRenewal && '20' @ok="handleSignFinish"/></template><script>
import SginWindow from './SginWindow'
export default {data(){return {components: { SginWindow },showSign: false,methods: {async handleSignFinish() {// 执行逻辑---子传父},}}}
}
</script>

canvas--如何实现H5移动端电子签名相关推荐

  1. 解决canvas动画嵌套H5移动端适配、嵌套iframe加载canvas动画屏幕适配的问题

    目录 一.背景是用iframe来加载canvas动画来做背景 二.效果图 三.如果没有做canvas的兼容H5DE 适配就会出现这种情况 四.解决方案:在iframe加载的canvas源码里面添加这几 ...

  2. pdfh5.js 基于pdf.js和jQuery,web/h5/移动端PDF预览手势缩放插件。

    pdfh5.js 基于pdf.js和jQuery,web/h5/移动端PDF预览手势缩放插件. 注意:本地绝对路径地址不能加载,跨域问题用代理或者服务端解决. svg模式渲染存在缺陷,只能渲染普通pd ...

  3. H5 移动端二维码扫描

    H5 移动端二维码扫描-附加扫描样式 依赖包jsQR 思路:通过浏览器获取视频流播放在video当中,然后设置定时器截取视频流绘制在canvas中,使用canvas获取到图片信息,再使用jsQR解析二 ...

  4. H5 移动端调取手机相机或相册

    H5 移动端调取手机相机或相册 1.html代码如下: <!--图片二--><label for="xFile2" style="padding-top ...

  5. android h5 禁止缩放,vue h5移动端禁止缩放代码

    vue h5移动端禁止缩放代码 安卓 在index.html里面写 ios 在APP.vue里面写 window.onload = function() { document.addEventList ...

  6. H5 移动端 获取腾讯地图计算两经纬度的实际距离(可批量)_多地打卡

    文章目录 一.H5移动端 1. 安装vue-jsonp 2. 引入腾讯sdk 3. 实例化 4. 二点求距离 5. 多点求距离 文档地址: https://lbs.qq.com/service/web ...

  7. php清除h5格式,移动端H5页面端怎样除去input输入框的默认样式

    这次给大家带来移动端H5页面端怎样除去input输入框的默认样式,移动端H5页面端除去input输入框的默认样式的注意事项有哪些,下面就是实战案例,一起来看一下. 前两天在开发在微信访问的HTML5页 ...

  8. html5首页图标怎么除掉,移动端H5页面端如何除去input输入框的默认样式

    移动端H5页面端如何除去input输入框的默认样式 发布时间:2020-09-29 16:41:58 来源:亿速云 阅读:124 作者:小新 这篇文章主要介绍了移动端H5页面端如何除去input输入框 ...

  9. H5移动端项目案例、web手机微商城实战开发

    自微信生态圈一步步强大后,关于移动端购物的趋势,逐渐成为大众关心的内容,目前市场上关于移动商城的制定就有大量版本,比如.微商城.移动商城.移动webAPP.微信商城各等各种定义层出不穷,这就对于移动端 ...

最新文章

  1. 无法打开物理文件 X.mdf。操作系统错误 5:5(拒绝访问。)
  2. SNMP之管理信息库
  3. 如何在Oracle中复制表结构和表数据
  4. Boost:基于boost::asio模块引用计数程序
  5. 第一个structs+spring+hibernate的web程序
  6. HDU3388(二分+容斥原理)
  7. 文档数据库RavenDB-介绍与初体验
  8. 【es】es 分布式一致性原理剖析 节点篇
  9. babel原理_手写webpack核心原理,再也不怕面试官问我webpack原理
  10. 订阅号、服务号与企业号区别
  11. OpenGL编程指南 示例笔记(2)--独立地移动光源
  12. QOpenGLWidget显示图片
  13. 人事档案的重要性及注意事项
  14. 美国不道德的人体实验
  15. c语言实参和形参占用存储单元_在C语言中,以下说法正确的是()。 A.实参和与其对应的形参分别占用独立的存储单元。 B.实参和与...
  16. python scratch unity_极客晨星:少儿编程热门语言,除了Scratch还有哪些
  17. outlook登录QQ邮箱
  18. java大数据量调优(超赞值得收藏)
  19. 【应用场景】智能定位胸牌帮你识别高质量月嫂
  20. Problem:博览购票

热门文章

  1. 【21新生必看】重庆邮电大学联通、电信、移动校园网络套餐如何选择
  2. chapter.pandas1.1
  3. 分享最新联发科Helio P90数据手册,MT6779 datasheet/规格书
  4. CPU处理器 术语解释 外频
  5. 为什么用恒流电源驱动LED灯具
  6. 初始化Vant Weapp小程序
  7. 银行兴起数字极简风:“智能手机App恐惧症”终于有救了
  8. BZOJ 2999 inint【数DP优化】(Ender的模拟赛)
  9. firebase使用_如何使用Firebase和React(Hooks)构建实时聊天室
  10. 学生成绩abcde怎样划分_小学生成绩abcd四个等级怎样划分