公众号【码农爱摸鱼】,专注于在摸鱼中愉快的工作和学习~
手写签名(H5\小程序)
手写签名主要是用canvas实现。通过监听手指触碰屏幕事件来完成(touchstart、touchmove、touchcancel)
H5:
这里是用的vue封装一个手写签名的组件 话不多说直接上代码 html:
xml
<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:
kotlin
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无法完美的强制让浏览器横屏,所以这里使用媒体查询来达到横屏的效果。
css
@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,则:
json
{
"path" : "路径",
"style" : {
"navigationBarTitleText": "手写签名",
"pageOrientation": "landscape" // 属性值与上面一致
}
}
注意!!!小程序在横屏的时候,页面可视区域发生了变化,所以在写css样式的时候需要注意。
这里可以使用vmin。vmin是一种视窗单位,也是相对单位,取的是视图窗口宽度百分比vw和高度百分比vh的较小值。使用vmin的好处是可以让小程序页面不论是横屏还是竖屏,字体大小都能保持一致。
我开发小程序用的uni-app,首先声明一个scss函数:
perl
@function tovmin($rpx){//$rpx为需要转换的字号
@return #{$rpx * 100 / 750}vmin;
}
使用时就把原本单位为rpx的数值带入函数即可,例如:40rpx ==> tovmin(40);
和H5的canvas不同,小程序只需要改成这样:
kotlin
<canvas id="signCanvas" canvas-id="signCanvas" class="sign_canvas" disable-scroll @touchstart='handlerTouchstart' @touchmove='handlerTouchmove' @touchend='handlerTouchend' @touchcancel='handlerTouchend'></canvas>
disable-scroll这个属性是为了防止在手指滑动时页面发生移动 。 js:
kotlin
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)
}
})
})
},
},
我是摸鱼君,你的【三连】就是摸鱼君创作的最大动力,如果本篇文章有任何错误和建议,欢迎大家留言!
文章持续更新,可以微信搜索 【码农爱摸鱼】关注公众号第一时间阅读。