手写签名(H5\小程序)

做个笔记!

手写签名主要是用canvas实现。通过监听手指触碰屏幕事件来完成(touchstart、touchmove、touchcancel)

H5:

这里是用的vue封装一个手写签名的组件

html:

<template>
  <div class="hand_paint" @touchmove.prevent>
    <div class="hand_content">
      <div class="sign_box" id="signBox">
        <canvas id="signCanvas"></canvas>
      </div>

      <div class="btn_boxs">
        <div @click="clear()">重签</div>
        <div @click="save()">确定</div>
      </div>
    </div>
  </div>
</template>

js:

data() {
    return {
      image: "",
      mousePressed: false, // 是否触碰画板
      c: "",
      ctx: "",

      lastX: 0,
      lastY: 0,
    }
  },
  mounted() {
    this.image = "";
    this.mousePressed = false;

    this.ctx = document.getElementById("signCanvas").getContext("2d");
    this.c = document.getElementById("signCanvas");
    var signBox = document.getElementById("signBox");
    console.log('clientWidth', signBox.clientWidth)

    this.c.width = signBox.clientWidth; // 设置宽度
    this.c.height = signBox.clientHeight; // 设置高度
    console.log('this.c', this.c.width)

    // 监听touchstart事件,touchmove事件,touchcancel事件等事件
    this.InitThis();
  },
  methods: {
    InitThis() {
      // 触摸屏
      var _this = this;
      const WIDTH = document.body.clientWidth
      this.c.addEventListener("touchstart", function(event){
        if (event.targetTouches.length == 1) {
          var touch = event.targetTouches[0];
          _this.mousePressed = true;

          _this.Draw(
            touch.pageY - this.offsetLeft,
            WIDTH - this.offsetTop - touch.pageX,
            false
          );
        }
      }, false );
      this.c.addEventListener("touchmove", function(event){
        if (event.targetTouches.length == 1) {
          var touch = event.targetTouches[0];
          if (_this.mousePressed) {
            _this.Draw(
              touch.pageY - this.offsetLeft,
              WIDTH - this.offsetTop - touch.pageX,
              true
            );
          }
        }
      }, false );
      this.c.addEventListener("touchcancel", function(event){
        if (event.targetTouches.length == 1) {
          _this.mousePressed = false;
        }
      }, false );
    },
    // 绘画
    Draw(x, y, isDown) {
      if (isDown) {// 开始移动手指
        this.ctx.beginPath();
        this.ctx.strokeStyle = "#000"; // 颜色
        this.ctx.lineWidth = 3; // 线宽
        this.ctx.lineJoin = "round"; // 拐角类型
        this.ctx.lineMax = 10; // 设置画笔最大线宽
        this.ctx.lineMin = 3; // 设置画笔最小线宽
        this.ctx.linePressure = 1.2; // 设置画笔笔触压力
        this.ctx.smoothness = 30; // 设置画笔笔触大小变化的平滑度
        this.ctx.moveTo(this.lastX, this.lastY);
        this.ctx.lineTo(x, y);
        this.ctx.closePath();
        this.ctx.stroke();
      }
      this.lastX = x;
      this.lastY = y;
    },
    // 清空画板
    clear() {
      this.ctx.setTransform(1, 0, 0, 1, 0, 0);
      this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    },
    // 提交签名
    save() {
      this.checkEmpty(); // 非空验证
    },
    checkEmpty() {
      var c = document.getElementById("signCanvas"); // 获取canvas对象
      if (this.isCanvasBlank(c)) {
        return;
      } else {
        var image = this.c.toDataURL("image/png"); // 得到生成后的签名base64位  url 地址
        // console.log(image); // 打印图片base64 url
        this.dataURLtoFile(image, 'signature.png')
      }
    },
    dataURLtoFile(dataurl, filename) {//将base64转换为文件,dataurl为base64字符串,filename为文件名(必须带后缀名,如.jpg,.png)
      var arr = dataurl.split(','),
          mime = arr[0].match(/:(.*?);/)[1],
          bstr = window.atob(arr[1]),
          n = bstr.length,
          u8arr = new Uint8Array(n);
      while(n--){
        u8arr[n] = bstr.charCodeAt(n);
      }
      let file = new File([u8arr], filename, {type:mime});
      this.aggSignUpload(dataurl, file)
    },
    aggSignUpload(dataurl, file){
      let _this = this

      let formData = new FormData()
      formData.append('file', file);

      // 调用后端接口,通过文件流的方式上传图片
      // 成功回调中将图片base64传回父组件用作展示
          _this.$emit('submit', dataurl)

    },
    // 验证canvas画布内容是否为空
    isCanvasBlank(canvas) {
      var blank = document.createElement("canvas"); // 创建一个空canvas对象
      blank.width = canvas.width;
      blank.height = canvas.height;
      return canvas.toDataURL() == blank.toDataURL(); // 比较值相等则为空
    },
  },

css:

由于H5无法完美的强制让浏览器横屏,所以这里使用媒体查询来达到横屏的效果。

@media screen and (orientation: portrait) { // 当屏幕处于纵向时
  .hand_paint {
    position: absolute;
    width: 100vh;
    height: 100vw;
    top: 0;
    left: 100vw;
    -webkit-transform: rotate(90deg);
    -moz-transform: rotate(90deg);
    -ms-transform: rotate(90deg);
    transform: rotate(90deg);
    transform-origin: 0% 0%;
  }
}

小程序:

小程序与H5不同,没有封装为组件,做成了一个公共页面。因为可以在page的json文件中设置:

横屏 "pageOrientation": "landscape"

竖屏 "pageOrientation": "portrait" (默认)

自动切换横竖屏 "pageOrientation": "auto"

如果是用的uni-app,则:

{
            "path" : "路径",
            "style" : {
                "navigationBarTitleText": "手写签名",
                "pageOrientation": "landscape"  // 属性值与上面一致
            }
}

注意!!!小程序在横屏的时候,页面可视区域发生了变化,所以在写css样式的时候需要注意。

这里可以使用vmin。vmin是一种视窗单位,也是相对单位,取的是视图窗口宽度百分比vw和高度百分比vh的较小值。使用vmin的好处是可以让小程序页面不论是横屏还是竖屏,字体大小都能保持一致。

我开发小程序用的uni-app,首先声明一个scss函数:

@function tovmin($rpx){//$rpx为需要转换的字号
    @return #{$rpx * 100 / 750}vmin;
}

使用时就把原本单位为rpx的数值带入函数即可,例如:40rpx ==> tovmin(40);

和H5的canvas不同,小程序只需要改成这样:

<canvas id="signCanvas" canvas-id="signCanvas" class="sign_canvas" disable-scroll @touchstart='handlerTouchstart' @touchmove='handlerTouchmove' @touchend='handlerTouchend' @touchcancel='handlerTouchend'></canvas>

disable-scroll这个属性是为了防止在手指滑动时页面发生移动 。

js:

data() {
    return {
      mousePressed: false, // 是否触碰画板
      c: "",
      ctx: "",
      
	  arrX: [],
	  arrY: [],
	  arrZ: [],
	  isPaint: false, // 是否绘画
			
	  type: ''
    }
},
onReady() {
	this.mousePressed = false;
	
		
	this.ctx = uni.createCanvasContext('signCanvas');
	this.ctx.lineWidth = 3; // 线宽
	this.ctx.lineCap = 'round'; // 线条端点类型
	this.ctx.lineJoin = 'round'; // 线条拐角类型
	this.ctx.globalAlpha = 1; // 透明度
	console.log('ctx',this.ctx)
},
methods: {
	handlerTouchstart(event){
		this.arrX = []
		this.arrY = []
		this.arrZ = []
		if (event.changedTouches.length == 1) {
			 var touch = event.changedTouches[0];
			 // console.log('touchstart',event)
			 this.mousePressed = true;
			
			 this.arrZ.push(0);
			 this.arrX.push(Math.floor(touch.x));
			 this.arrY.push(Math.floor(touch.y));
		}
	},
	handlerTouchmove(event){
		if (event.changedTouches.length == 1) {
			 var touch = event.changedTouches[0];
			 if (this.mousePressed) {
			   this.arrZ.push(1);
			   this.arrX.push(Math.floor(touch.x));
			   this.arrY.push(Math.floor(touch.y));
					
				// 绘画
				for (var i = 0; i < this.arrZ.length; i++) {
					if (this.arrZ[i] == 0) {
						this.ctx.moveTo(this.arrX[i], this.arrY[i])
					} else {
						this.ctx.lineTo(this.arrX[i], this.arrY[i])
					};
				};
				this.ctx.stroke();
				this.ctx.draw(true);
				this.isPaint = true
			 }
		}
			
			
	},
	handlerTouchend(event){
		if (event.changedTouches.length == 1) {
			 this.mousePressed = false;
		}
	},
    // 清空画板
    clearArea() {
			let canvasw, canvash;
			const res = uni.getSystemInfoSync();

			canvasw = res.windowWidth * 1.2; //设备宽度
			canvash = res.windowHeight * 1.2; //设备高度
			console.log('canvasw',canvasw)
			console.log('canvash',canvash)

			this.arrX = []
			this.arrY = []
			this.arrZ = []
			this.isPaint = false
			this.ctx.clearRect(0, 0, canvasw, canvash);
			this.ctx.draw(true);
    },
    // 提交签名
    saveSign() {
            let _this = this
			if (!this.isPaint) { // 画布为空
				return false;
			};
			//生成图片
			const fileManager = wx.getFileSystemManager();
			uni.showLoading({title: '加载中', mask: true})
			uni.canvasToTempFilePath({
				x: 0,
				y: 0,
				canvasId: 'signCanvas',
				success: function (res) {
					//将图片转换为base64 的格式
					uni.hideLoading()
					let base64 = 'data:image/jpg;base64,' + fileManager.readFileSync(res.tempFilePath, 'base64');
					// console.log(base64)
					//图片路径
					// console.log('tempFilePath', res.tempFilePath)
					_this.uploadImg(res.tempFilePath, base64)
				}
			})
    },
    // 上传图片文件
	uploadImg(tempFilePath, base64){
		uni.showLoading({
			 title: '上传中'
		});
		let _this = this
		return new Promise((resolve, reject) => {
			uni.uploadFile({
				method: "post",
				header: {
					"token": userinfo.token,
					"Content-Type": "multipart/form-data"
				},
				url: '接口地址',
				filePath: tempFilePath, // 生成的文件路径
				name: 'file',
				formData: '请求参数',
				success: (res) => {
					uni.hideLoading()
					console.log('res', res)
				},
				fail(err) {
					uni.hideLoading()
					console.log('err', err)
				}
			})
		})
	},

},

如有兴趣探讨技术,请关注公众号

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值