1. 背景
在使用uni-app搬砖过程中,有一个需要签名的需求,于是有了以下代码。
2. 代码实现
<template>
<uni-popup ref="popup" background-color="#fff" :mask-click="false">
<view class="popup-content">
<!-- 签名横屏 -->
<view class="signa">
<view class="box">
<canvas
class="canvas"
disable-scroll="true"
:style="{ width: width, height: height }"
canvas-id="designature"
@touchstart="starts"
@touchmove="moves"
></canvas>
<view class="bottomBox u-flex">
<view class="tip">请你在此手动签写自己的签章,签写操作完毕点击保存按钮</view>
<view class="btn" style="margin-top: 18vw" @click="cancel">取消</view>
<view class="btn" style="margin-top: 8vw" @click="clear">清除</view>
<view class="btn blue" @click="save">提交</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script>
export default {
name: 'FSignature',
model: { prop: 'value', event: 'change' },
props: {
value: { type: Boolean },
},
watch: {
value(val) {
this.$refs.popup[val ? 'open' : 'close']()
},
},
data() {
return {
resultUrl: '',
dom: null,
line: [],
width: '0px',
height: '0px',
radius: 0,
isSign: false, //是否签名
}
},
created() {
uni.getSystemInfo({
success: (res) => {
this.width = res.windowWidth - 56 + 'px'
this.height = res.windowHeight - 154 + 'px'
},
})
this.dom = uni.createCanvasContext('designature', this)
},
methods: {
cancel() {
this.$emit('change', false)
this.isSign = false
},
distance(a, b) {
let x = b.x - a.x
let y = b.y - a.y
return Math.sqrt(x * x + y * y)
},
starts(e) {
this.line.push({
points: [
{
time: new Date().getTime(),
x: e.touches[0].x,
y: e.touches[0].y,
dis: 0,
},
],
})
let currentPoint = {
x: e.touches[0].x,
y: e.touches[0].y,
}
this.currentPoint = currentPoint
this.drawer(this.line[this.line.length - 1])
this.isSign = true
},
moves(e) {
let point = {
x: e.touches[0].x,
y: e.touches[0].y,
}
;(this.lastPoint = this.currentPoint), (this.currentPoint = point)
this.line[this.line.length - 1].points.push({
time: new Date().getTime(),
x: e.touches[0].x,
y: e.touches[0].y,
dis: this.distance(this.currentPoint, this.lastPoint),
})
this.drawer(this.line[this.line.length - 1])
},
drawer(item) {
let x1,
x2,
y1,
y2,
cx,
cy,
t = 0.5,
x,
y
var time = 0
if (item.points.length > 2) {
let lines = item.points[item.points.length - 3]
let line = item.points[item.points.length - 2]
let end = item.points[item.points.length - 1]
x = line.x
y = line.y
x1 = lines.x
y1 = lines.y
x2 = end.x
y2 = end.y
var dis = 0
time = line.time - lines.time + (end.time - line.time)
dis = line.dis + lines.dis + end.dis
var dom = this.dom
var or = Math.min((time / dis) * this.linePressure + this.lineMin, this.lineMax)
cx = (x - Math.pow(1 - t, 2) * x1 - Math.pow(t, 2) * x2) / (2 * t * (1 - t))
cy = (y - Math.pow(1 - t, 2) * y1 - Math.pow(t, 2) * y2) / (2 * t * (1 - t))
dom.setLineCap('round')
dom.beginPath()
dom.setStrokeStyle('black')
dom.setLineWidth(5)
dom.moveTo(x1, y1)
dom.quadraticCurveTo(cx, cy, x2, y2)
dom.stroke()
dom.draw(true)
}
},
clear() {
this.isSign = false
this.dom.clearRect(0, 0, 1000, 1000)
this.dom.draw()
},
save() {
var t = this
if (!this.isSign) {
uni.showToast({ title: '请签名', icon: 'none' })
return
}
uni.canvasToTempFilePath(
{
canvasId: 'designature',
fileType: 'png',
quality: 1, //图片质量
success: (res) => {
this.rotateBase64Img(res.tempFilePath, 270, this.success)
},
fail(e) {
console.log(e)
},
},
this,
)
},
// 旋转成功后的回调
success(sign) {
this.$emit('getSign', sign)
setTimeout(() => {
this.$emit('change', false)
}, 500)
},
// 当前为横屏签字,需旋转图片
rotateBase64Img(src, edg, callback) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
var imgW //图片宽度
var imgH //图片高度
var size //canvas初始大小
if (edg % 90 != 0) {
console.error('旋转角度必须是90的倍数!')
throw '旋转角度必须是90的倍数!'
}
edg < 0 && (edg = (edg % 360) + 360)
const quadrant = (edg / 90) % 4 //旋转象限
const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } //裁剪坐标
var image = new Image()
image.crossOrigin = 'anonymous'
image.src = src
image.onload = function () {
imgW = image.width
imgH = image.height
size = imgW > imgH ? imgW : imgH
canvas.width = size * 2
canvas.height = size * 2
switch (quadrant) {
case 0:
cutCoor.sx = size
cutCoor.sy = size
cutCoor.ex = size + imgW
cutCoor.ey = size + imgH
break
case 1:
cutCoor.sx = size - imgH
cutCoor.sy = size
cutCoor.ex = size
cutCoor.ey = size + imgW
break
case 2:
cutCoor.sx = size - imgW
cutCoor.sy = size - imgH
cutCoor.ex = size
cutCoor.ey = size
break
case 3:
cutCoor.sx = size
cutCoor.sy = size - imgW
cutCoor.ex = size + imgH
cutCoor.ey = size + imgW
break
}
ctx.translate(size, size)
ctx.rotate((edg * Math.PI) / 180)
ctx.drawImage(image, 0, 0)
var imgData = ctx.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey)
if (quadrant % 2 == 0) {
canvas.width = imgW
canvas.height = imgH
} else {
canvas.width = imgH
canvas.height = imgW
}
ctx.putImageData(imgData, 0, 0)
callback(canvas.toDataURL())
}
},
},
}
</script>
<style scoped lang="scss">
.signa {
overflow: hidden;
background-color: #fff;
height: 100vh;
width: 100vw;
padding: 20rpx 30rpx;
.box {
background-color: #fff;
border-radius: 20rpx;
padding: 24rpx;
box-shadow: 0 6rpx 42rpx rgba(0, 0, 0, 0.05);
}
.canvas {
background-color: #e6e6e6;
position: relative;
z-index: 9999;
}
.bottomBox {
flex-direction: column;
justify-content: center;
width: 200rpx;
height: 35vw;
transform: rotate(90deg) translateY(-390rpx);
.tip {
font-size: 22rpx;
color: #ff6f77;
}
.btn {
width: 100%;
height: 74rpx;
background: #f5f5f5;
border-radius: 40rpx;
margin-top: 14rpx;
text-align: center;
line-height: 74rpx;
color: #333333;
font-size: 26rpx;
&.blue {
background: #284cf4;
color: #fff;
}
}
}
}
</style>
3. 使用方法
<FSignature v-model="isShowSign" @getSign="getSign" />