手签功能(一字一签)
结果样式
图片:
生成的签字图片
详细代码如下
手签组件 (个人命名为 handwriting.vue)
<template>
<view class="sign-by-hand">
<view class="view-pop">
<view class="view-title">
<view style="flex: 1;"></view>
<view style="flex: 3; text-align: center; font-size: 45rpx;">{{signTitle}}</view>
<view style="flex: 1; display: flex; justify-content: flex-end; padding-right: 8rpx;">
<u-icon name="close" color="#aaaaaa" size="45rpx" @click="$noMultipleClicks(handClose)"></u-icon>
</view>
</view>
<view class="sign-contents">
<view style="position: relative;">
<view class="view-item" >
<view class="view-words" v-for="(item,index) in listImage" :key="index">
<view class="view-text" :style="item.boder == 'processing'? 'color: #328E0E' : ''">{{item.text}}</view>
</view>
</view>
<view class="view-item view-position">
<view class="view-words" v-for="(item, index) in listImage" :key="index">
<view v-if="item.boder === 'processing'" class="img-view status-active">
<image :src="item.img" mode="aspectFit" class="sign-img"/>
</view>
<view v-else class="img-view status-default" :class="index > currentSignIndex ? 'status-default-l' : index < currentSignIndex ? 'status-default-r' :'status-default-lr'">
<image :src="item.img" mode="aspectFit" class="sign-img" :style="item.img? 'background: #FFF' : ''"/>
</view>
</view>
</view>
</view>
</view>
<view style="color: #AAAAAA; font-size: 30rpx; text-align: center;">请在田字格中抄录上面的文字</view>
<view class="view-canvas">
<view class="canvas-bg" :style="'width:' + canvasWH +'px;' + 'height:' + canvasWH + 'px;'">
<canvas
class="mycanvas"
disable-scroll="true"
id="mycanvas"
type="2d"
@touchstart="touchstart"
@touchmove="touchmove"
@touchend="touchend">
</canvas>
</view>
</view>
<view class="view-butt">
<view class="butt-le" style="color: #cccccc;" @click.stop="clearDrawCanvas">清空</view>
<view class="butt-le" style="color: #6AB8F0;" @click.stop="finish">确认</view>
</view>
</view>
<view style='width:0px;height:0px;overflow:hidden;position: fixed;top: 999998rpx;'>
<canvas type="2d" id="handwritingCanvas"></canvas>
</view>
</view>
</template>
<script>
import can2d from './canvas2D.js';
let canvas, ctx = '';
export default {
name: "handwriting",
props: {
// 标题
signTitle: {
type: String,
default: ''
},
// 抄录的文字
signText: {
type: String,
default: ''
},
// 可用可不用
signType: {
type: String,
default: ''
},
// 画布的宽高
canvasWH: {
type: String,
default: ''
}
},
data() {
return {
noClick: true,
showType: 'a',
listImage: [], // 文字列表
points: [], // 路径点集合
srcImgXs: "", //签名图片地址
srcImgMap: [],
}
},
created() {
console.log('--==========>>>', this.canvasWH)
this.listTxtAndImgs(this.$props.signText);
this.clearBool = true;
this.points = [];
wx.createSelectorQuery().in(this).select('#mycanvas').fields({
node: true,
size: true
}).exec((res) => {
// Canvas 对象
this.canvas = res[0].node;
// 渲染上下文
this.ctx = this.canvas.getContext('2d');
const width = this.canvasWH;
const height = this.canvasWH;
// 初始化画布大小
const dpr = uni.getWindowInfo().pixelRatio;
const rpxRatio = 750 / uni.getSystemInfoSync().screenWidth;
this.canvas.width = width * dpr;
this.canvas.height = height * dpr;
this.ctx.scale(dpr, dpr);
this.ctx.lineWidth = 13; // 设置画布线的宽度
this.ctx.lineCap = "round"; // 设置画布线条末端圆角
this.ctx.lineJoin = "round"; // 设置画布线条开始圆角
})
},
methods: {
// 关闭手写框
handClose() {
this.$emit('close', {})
},
// 清空画布
clearDrawCanvas() {
let that = this;
uni.getSystemInfo({
success: function(res) {
that.points = [];
that.clearBool = true;
let canvasw = res.windowWidth;
let canvash = res.windowHeight;
that.ctx.clearRect(0, 0, canvasw, canvash);
},
})
},
//完成绘画并保存到本地
finish() {
const _this = this;
if (_this.clearBool) {
uni.showToast({
icon: 'none',
title: '请先进行签字再点击完成!',
duration: 2000
});
return;
}
let _i = 0;
if (this.listImage > 0) {
this.listImage.forEach((v, index) => {
if (v.img == '') {
_i++;
}
})
if (_i == 0) {
this.handClose();
return;
}
}
uni.canvasToTempFilePath({
canvas: this.canvas,
quality: 1,
fileType: 'png',
success: function(res) {
let path = res.tempFilePath;
console.log('-------->', path)
// 图片的名称
let photo_name = _this.$functionPub.getSubLastYxWx(path) + _this.$functionPub.getSubLastYx(path);
console.log('photo_name -------->', photo_name)
let _j = 0; //初始化记录的数据,用于记录操作中的数据
// 遍历获取图片列表 生成签字数组
_this.listImage.forEach((v, index) => {
// processing:处理中 leisure:闲置状态
if (v.boder === 'processing') {
let map = {
type: 1,
img: path,
text: v.text
}
_this.srcImgMap.push(map); // 存储第二次的手签图片列表
_j = index; // 记录当前处理中的下表
}
})
// 存储已绘制好的图片路径并改变状态为 闲置->leisure
_this.listImage[_j].img = path;
_this.listImage[_j].boder = "leisure";
_this.listImage[_j].name = photo_name;
_this.clearDrawCanvas(); // 清空画布
let _jj = _j + 1;
// 将当前目标的下一个目标设置为 处理->processing。如当前手签数量超出图片文字则将当前绘制的图片列表绘制成一个图片
if (_jj < _this.listImage.length) {
_this.listImage[_j + 1].boder = "processing"; // 当前需被处理的对象 processing
_this.currentSignIndex = _j + 1; // 记录当前正在处理的下标
} else {
_this.drawImgList(); // 执行将多张图片绘制成一张图片
return;
}
},
fail(e) {
console.log('fail and finish ---> ', e)
}
}, this)
},
// 单个区域的签字列表生成文字列表
drawImgList() {
uni.showLoading({
title: '签字处理中...',
mask: true
});
let _this = this;
const heightWidth = 330;
// 引入使用 handwritingCanvas firstCanvas
can2d.getMyCanvasAndCtx('firstCanvas').then(res => {
let canvas = res.canvas;
let ctx = res.ctx;
// 设置生成图片的宽度 图片的个数 * 图片的压缩后的宽度 + 间隔(测试时调整的宽度)
canvas.width = _this.listImage.length * heightWidth + (_this.listImage.length - 1) * 2;
// 设置生成图片的高度
canvas.height = heightWidth;
// console.log("canvas.width ------- ", canvas.width, canvas.height);
// 遍历手写文字图片并绘制在一张图片中
_this.listImage.forEach((v, index) => {
let _srcImg = canvas.createImage();
_srcImg.src = v.img;
let _mWid = 5;
if (index > 0) {
_mWid = 5 + 2 * index
}
_srcImg.onload = () => {
ctx.drawImage(_srcImg, _mWid + index * heightWidth, 0, heightWidth, heightWidth); //(图片对象, x位置, y位置, 宽度, 高度)
ctx.restore();
};
})
// 延迟一秒生成拼接图片
setTimeout(() => {
uni.canvasToTempFilePath({
quality: 1,
canvas: canvas,
fileType: 'png',
success: function(resFile) {
console.log('通过画布绘制出的图片--保存的就是这个图', resFile.tempFilePath)
let path = resFile.tempFilePath; // 绘制图片的路径
let photo_name = _this.$functionPub.getSubLastYxWx(path) + _this.$functionPub.getSubLastYx(path);
console.log("绘制图片的路径 ------", path);
// 置空用于操作后面的数据
_this.listImage = []; // 图片列表置空
_this.$emit('srcImg',{
photoName: photo_name,
pathFile: path,
typeName: _this.signType,
file: resFile
})
_this.handClose();
uni.hideLoading(); // 隐藏loading
},
fail: function(err) {
console.log("保存失败", err);
uni.hideLoading(); // 隐藏loading
}
}, _this)
}, 1000)
})
},
// 初始化签名对象
listTxtAndImgs(text) {
this.listImage = []; //初始化图片数组对象
let _text = text; //这是AB一段CD文字
if (text == 'qm') {
// this.showCancel();
console.log('------->>>>> 签名')
} else {
// let reg = /[\u4e00-\u9fa5]/g; //中文判断
// let textList = _text.match(reg); //返回中文的拆分后的数组
// console.log('------->>>>>', _text)
let textList = _text.split(""); //返回中文的拆分后的数组
let _v = "";
textList.forEach((v, index) => { //遍历设置图片数组对象
// console.log('textList forEach v index ---->', v, index)
if (index == 0) {
_v = "processing"; // 初始化第一个为需被处理的对象 processing
this.currentSignIndex = index; // 记录当前正在处理的下标
} else {
_v = "leisure"; // 当前需被处理的对象 leisure
}
this.listImage.push({
text: v,
img: "",
boder: _v,
name: ""
})
})
// console.log('current listImage---->', this.listImage);
}
},
//触摸开始,获取到起点
touchstart(e) {
let startX = e.changedTouches[0].x;
let startY = e.changedTouches[0].y;
let startPoint = {
X: startX,
Y: startY
};
if (startX != undefined && startY != undefined) {
let startPoint = {
X: startX,
Y: startY
};
if (startPoint != undefined) {
this.points.push(startPoint);
//每次触摸开始,开启新的路径
this.ctx.beginPath();
this.clearBool = false;
}
}
},
//触摸移动,获取到路径点
touchmove(e) {
let moveX = e.changedTouches[0].x;
let moveY = e.changedTouches[0].y;
if (moveX != undefined && moveY != undefined) {
let movePoint = {
X: moveX,
Y: moveY
};
if (movePoint != undefined) {
this.points.push(movePoint); //存点
let len = this.points.length;
if (len >= 2) {
this.draw(); //绘制路径
}
this.clearBool = false;
}
}
},
// 触摸结束,将未绘制的点清空防止对后续路径产生干扰
touchend() {
this.points = [];
this.clearBool = false;
},
/* ***********************************************
# 绘制笔迹
# 1.为保证笔迹实时显示,必须在移动的同时绘制笔迹
# 2.为保证笔迹连续,每次从路径集合中区两个点作为起点(moveTo)和终点(lineTo)
# 3.将上一次的终点作为下一次绘制的起点(即清除第一个点)
************************************************ */
draw() {
if (this.points.length > 1) {
let point1 = this.points[0]
let point2 = this.points[1]
this.points.shift()
// this.ctx.translate(0.1, 0.1); // 对半像素进行偏移校正
this.ctx.moveTo(point1.X, point1.Y)
this.ctx.lineTo(point2.X, point2.Y)
this.ctx.stroke()
}
},
}
}
</script>
<style lang="scss" scoped>
.sign-by-hand{
display: flex;
justify-content: center;
align-items: center;
background: #FFFFFF;
border-radius: 10rpx;
}
.view-pop {
width: 92vw;
.view-title {
padding: 20rpx;
display: flex;
flex-flow: nowrap;
align-items: center;
justify-content: space-between;
}
.sign-contents {
padding: 40rpx 20rpx 20rpx 20rpx;
// height: 90rpx;
display: flex;
justify-content: center;
.view-item {
display: flex;
justify-content: space-around;
flex-flow: row wrap;
align-items: center;
width: 550rpx;
}
.view-position {
position: absolute;
top: 0;
left: 0;
}
.view-words {
align-items: center;
justify-content: center;
.view-text {
width: 90rpx;
font-size: 50rpx;
color: #DDDDDD;
line-height: 90rpx;
text-align: center;
margin: 0 0rpx;
border: 1rpx solid transparent;
}
.img-view {
margin: 0rpx 0rpx;
height: 90rpx;
align-items: center;
justify-content: center;
}
.sign-img {
width: 90rpx;
height: 90rpx;
}
.sign-name {
margin: 0rpx 0rpx;
height: 120rpx;
align-items: center;
justify-content: center;
}
.sign-name-img {
width: 120rpx;
height: 120rpx;
}
.status-active {
border: 1px solid #328E0E;
}
.status-default {
border-top: 1px solid #AAAAAA;
border-bottom: 1px solid #AAAAAA;
}
.status-default-lr {
border-left: 1px solid #AAAAAA;
border-right: 1px solid #AAAAAA;
}
.status-default-l {
border-left: 1px solid #FFFFFF;
border-right: 1px solid #AAAAAA;
}
.status-default-r {
border-left: 1px solid #AAAAAA;
border-right: 1px solid #FFFFFF;
}
}
}
.view-butt {
display: flex;
flex-flow: nowrap;
align-items: center;
justify-content: space-around;
// margin: 30rpx 20rpx 0 20rpx;
.butt-le {
flex: 1;
padding: 20rpx 30rpx;
font-size: 36rpx;
text-align: center;
}
.butt-ri {
padding: 0 10rpx;
font-size: 36rpx;
}
}
.view-canvas {
margin-top: 20rpx;
margin-bottom: 30rpx;
display: flex;
justify-content: center;
align-items: center;
.img-bg{
position: fixed;
border: 4rpx solid #ED5A5A;
width: 100%;
height: 100%;
}
.canvas-bg{
background-image: url("../../../static/images/tin_word.png");
background-repeat: no-repeat;
background-size: cover;
border: 4rpx solid #ED5A5A;
.mycanvas {
width: 100%;
height: 100%;
}
}
}
}
</style>
相关的使用到的JS文件 canvas2D.js
function getMyCanvasAndCtx(id) {
return new Promise(resolve => {
const query = uni.createSelectorQuery()
query.select(`#${id}`).fields({
node: true,
size: true
}).exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
const dpr = uni.getSystemInfoSync().pixelRatio;
canvas.width = res[0].width * dpr;
canvas.height = res[0].height * dpr;
ctx.scale(dpr, dpr);
let data = {
canvas: canvas,
ctx: ctx
}
resolve(data)
})
})
}
export default {
getMyCanvasAndCtx,
}
文件放置方式如下:
在组件引用如下
<template>
<view>
....
<view @click="showHandleSign">开始签字</view>
<!-- 图片拼接时使用 -->
<view style='width:0px;height:0px;overflow:hidden;position: fixed;top: 999999rpx;'>
<canvas type="2d" id="firstCanvas" ></canvas>
</view>
<!-- 手签 一字一签 -->
<view v-if="show" mode="center" class="sign-one-by-one">
<handwriting :signTitle="signTitle" :signText="signText" :signType="'bryqr'" :canvasWH="signCanvasWH" @close="handClose" @srcImg="srcImg"></handwriting>
</view>
</view>
</template>
<script>
...
import handwriting from '../../../components/common/handwriting/handwriting.vue';
export default {
components: {
handwriting
},
data() {
return {
srcImgQr: "", //本人已确认 图片地址
signCanvasWH: 330, //初始化canvas 宽度
srcImgjp: "", //截屏图片
// 手签样式
show: false,
signTitle: "文字抄录",
signText: '本人已确认',
listImage: [],
...
};
},
beforeCreate() {
},
created() {
let that = this
uni.getSystemInfo({
success: res => {
// console.log("created getSystemInfo -----",res)
that.canvasWidth1 = res.screenWidth;
that.ratio = 750 / res.screenWidth;
// console.log("that.ratio ----- ",that.ratio);
that.canvasHeight1 = 1000 / that.ratio;
that.signCanvasWH = res.screenWidth - 100;
let statusBarHeight = res.statusBarHeight; //手机状态栏高度
let isiOS = res.system.indexOf('iOS') > -1; //是否为iOS系统
let barHeight = !isiOS ? 48 : 44; //导航栏高度,iOS:48,Android:44
that.myCanvasHeight = res.screenHeight - barHeight - statusBarHeight;
}
})
},
methods: {
// 显示手签
showHandleSign() {
this.signText = '本人已确认';
this.show = true;
},
// 关闭弹框组件
handClose(){
this.show = false;
},
// 接收签字图片
srcImg(e) {
console.log("srcImg ----- >>>>", e);
this.srcImgQr = e.pathFile;
},
}
...
}
</script>
<style lang="scss" scoped>
...
// css 样式
.sign-one-by-one {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
// 把页面按钮隐藏掉
opacity: 1;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.8);
z-index: 9999 !important;
}
</style>
本文章是个人项目中使用的情况,如有UI样式不同可自行修改。如有问题请私信 3Q!