微信小程序canvas实现简易手写签名版(uni-app)

微信小程序可以通过canvas实现手写签名的效果,本文中使用的是微信小程序Canvas 2D接口
本示例中绘制的是横屏签名的效果,效果图如下:
在这里插入图片描述
这里我们需要调整canvas的物理宽高,默认物理宽高为300*150px,物理宽高调整通过css样式即可,本文中需要根据屏幕高度进行动态调整,使用的是行内样式
页面布局:

<template>
	<view class="sign-page" :style="{paddingTop: top + 'px'}">
		<view class="canvas-box">
			<view class="left-pane">
				<view class="f28 text-gray6 left-text">请签字确认</view>
				<view class="right-box">
					<view class="left-button" @click="clearContext">
						<text class="ic ic-delete text-gray6"></text>
						<text class="f30 text-gray6 ml5">清空</text>
					</view>
					<button class="right-button" @click="confirm">完成</button>
				</view>
			</view>
			<!-- canvas的物理宽高可通过样式调整 -->
			<canvas
				class="canvas"
				disable-scroll 
				type="2d" 
				id="myCanvas"
				@touchstart="handleTouchstart"
				@touchmove="handleTouchmove"
				:style="{
					width: canvasWidth + 'px', 
					height: canvasHeight + 'px'}">
			</canvas>
			
			<view class="right-pane">
				<view class="dis-flex back-button" @click="back">
					<text class="ic ic-left text-gray6"></text>
					<text class="text-gray6 ml15">取消</text>
				</view>
				<view class="title">
					<text class="text text-gray6">{{title || '检测人员'}}</text>
				</view>
			</view>
		</view>
		<canvas
			class="canvas2"
			disable-scroll 
			type="2d" 
			id="myCanvas2"
			:style="{
				width: canvasHeight + 'px', 
				height: canvasWidth + 'px'}">
		</canvas>
	</view>
</template>

js代码:canvas的物理宽高调整后,canvas的逻辑宽高也需要进行调整,默认逻辑宽高是300*150px,(小程序Canvas 2D接口支持修改逻辑宽高),具体参考本文中的initCanvas方法


<script>
	export default {
		data() {
			return {
				canvasWidth: 300,
				canvasHeight: 150,
				top: 0,
				canvas: null,
				title: ''
			}
		},
		onLoad() {
			const menuData = uni.getMenuButtonBoundingClientRect()
			uni.getSystemInfo({
			  success: (res) => {
			    let navPadding = menuData.top - res.statusBarHeight
			    // 顶部高度 = 状态栏高度 + 胶囊按钮行高度 + 胶囊按钮上下的padding
			    let navHeight = res.statusBarHeight + navPadding * 2 + menuData.height
			    // 设置canvas的物理宽高
			    this.canvasWidth = res.windowWidth - 100
				this.canvasHeight = res.windowHeight - navHeight - 20
				this.top = navHeight
			  }
			})
		},
		onReady() {
			this.initCanvas()
		},
		methods: {
			initCanvas() {
				uni.createSelectorQuery()
					.select('#myCanvas')
					.fields({ node: true, size: true })
					.exec((res) => {
						// 修改canvas的逻辑宽高
						// 如果不修改canvas的逻辑宽高,仅通过样式修改canvas的宽高,会导致绘图时比例不对,
						// 如将物理宽度改为600,但逻辑宽度还是300,假设画图时的起点x是100,那么实际看到的绘图起点是200
						const canvas = res[0].node
						this.canvas = canvas
						this.ctx = canvas.getContext('2d')
						// canvas.width = this.canvasWidth
						// canvas.height = this.canvasHeight
						// 注意:按照上面方式调整,虽然逻辑宽高和物理宽高保持一致了,但是会发现画出来的线会有锯齿不够清晰
						// 因为不同设备上物理像素与逻辑像素是不一致的
						// 因此canvas的逻辑宽高等于物理宽高分别*dpr
						const dpr = wx.getSystemInfoSync().pixelRatio
						canvas.width = this.canvasWidth * dpr
						canvas.height = this.canvasHeight * dpr
						// 假设dpr等于2,,那么canvas的物理宽度是600,逻辑宽度就是1200,
						// 假设画图时的起点x是100,那么实际看到的绘图起点是50,此时只需要将绘图内容进行等比例放大即可
						this.ctx.scale(dpr, dpr)
					})
			},
			handleTouchstart(e) {
				this.lineBegin(e.touches[0].x, e.touches[0].y)
			},
			handleTouchmove(e) {
				this.lineTo(e.touches[0].x, e.touches[0].y)
			},
			lineBegin(x, y) {
				this.ctx.beginPath()
				// 新版Canvas 2D接口,直接修改属性即可
				this.ctx.lineCap = 'round'
				this.ctx.lineWidth = 5
				this.startX = x
				this.startY = y
				this.ctx.moveTo(this.startX, this.startY)
			},
			lineTo(x, y) {
				this.ctx.lineTo(x, y)
				this.ctx.stroke()
				this.ctx.moveTo(x, y)
			},
			clearContext() {
				this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
			},
			confirm() {
				uni.canvasToTempFilePath({
					canvas: this.canvas, 
					success: (res) => {
						this.rotateImage(res.tempFilePath)
					}
				})
			},
			// 横屏签名,但是canvas的方向是垂直的,导出的图片也是竖屏,需要将图片进行旋转
			rotateImage(filePath) {
				uni.createSelectorQuery()
					.select('#myCanvas2')
					.fields({ node: true, size: true })
					.exec((res) => {
						// 首先绘制一个宽高与上面canvas相反的canvas
						const canvas = res[0].node
						this.canvas2 = canvas
						this.ctx2 = canvas.getContext('2d')
						
						const dpr = wx.getSystemInfoSync().pixelRatio
						canvas.width = this.canvasHeight * dpr
						canvas.height = this.canvasWidth * dpr
						this.ctx2.scale(dpr, dpr)
						// 绘制上述导出的签名图片到新的canvas上
						const img = this.canvas2.createImage()
						img.src = filePath
						img.onload = () => {
							// 签名图片旋转绘画解析在下方
							this.ctx2.translate(0, this.canvasWidth);
							this.ctx2.rotate(270 * Math.PI / 180)
							this.ctx2.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight)
							
							uni.canvasToTempFilePath({
								canvas: this.canvas2, 
								success: (res) => {
									this.handleUploadFile(res.tempFilePath)
								}
							}) 
						}						
					})
			},
			handleUploadFile(filePath) {
				uni.uploadFile({
		  			url: config.requestUrl + '/biz/file/upload/annex', 
			        filePath,
	  		        name: 'file',
					header: {
						'Authorization': getToken()
					},
				  	success: (res) => {
						// 调用接口成功
						if(res.statusCode == 200) {
							// 解析服务器返回数据
							const data = JSON.parse(res.data)
							if(data.code == 200) {
								const responseUrl = config.requestUrl + data.filePath
								const eventChannel = this.getOpenerEventChannel()
								eventChannel.emit('getSignImage', {filePath: responseUrl, fileId: data.fileId});
								this.back()
							}
						} else {
							uni.hideLoading()
						}
				  },
				  fail: (res) => {
				    uni.hideLoading()
				  }
				})
			},
			back() {
				uni.navigateBack()
			}
		}
	}
</script>	

由于签名的方向是横向的,但是canvas本身是竖向,导出的签名图片也为竖向,那么我们需要将导出的图片旋转为横向后重新绘制到canvas上然后导出。签名图旋转绘制的效果图如下:

图中黑色部分为canvas区域,竖向的签名图片为刚刚我们导出的图片,绘制到canvas上的效果如下图,如果我们要将其横向绘制到面板上,此时需要将绘画内容沿左上角顺时针旋转270deg,此时可以发现旋转后图片的覆盖区域在canvas向上移动canvasWdth的位置,那么旋转前将canvas的绘画起点重置到(0,canvasWidth)即可保证绘画内容旋转后刚好覆盖在canvas上
在这里插入图片描述
需要注意的点是,后面绘画旋转后的canvas在实际页面中我们是不需要看到的,需要通过样式将其隐藏,如果需要使用canvasToTempFilePath方法导出图片的话,不能使用display:none的隐藏canvas,否则会报错no image found,
可以通过定位和visiblity(opacity)属性隐藏。

页面样式:

<style lang="scss" scoped>
	.sign-page {
		min-height: 100vh;
		background-color: #f5f5f5;
		.canvas-box {
			position: relative;
			width: 100%;
		}
		.left-pane {
			width: 100rpx;
			.left-text {
				position: absolute;
				top: 0;
				line-height: 100rpx;
				transform: translateX(100rpx) rotate(90deg) ;
				transform-origin: 0 0;
			}
			.right-box {
				position: absolute;
				display: flex;
				align-items: center;
				bottom: 0;
				transform: translateX(100rpx) rotate(90deg)  translateX(-100%) translateX(100rpx);
				transform-origin: 0 0;
				.left-button {
					line-height: 100rpx;
					margin-right: 30rpx;
				}
				.right-button {
					font-size: 30rpx;
					color: #fff;
					width: 140rpx;
					height: 60rpx;
					line-height: 60rpx;
					background-color: green;
				}
			}
		}
		
		.canvas {
			margin: 0 auto;
			background-color: #fff;
			border: 2rpx dashed #d9d9d9;
			transform-origin: center center;
		}
		.canvas2 {
		/*设置display:none会导致wx.canvasToTempFilePath报错no image found*/
			/*display: none;*/
			position: absolute;
			opacity: 0;
		}
		.right-pane {
			position: absolute;
			width: 100rpx;
			height: 100%;
			right: 0;
			top: 0;
			.back-button {
				position: relative;
				z-index: 5;
				white-space: nowrap;
				line-height: 100rpx;
				align-items: center;
				transform: translateX(100rpx) rotate(90deg);
				transform-origin: 0 0;
			}
			.title {
				position: absolute;
				top: 0;
				z-index: 4;
				width: 100rpx;
				height: 100%;
				white-space: nowrap;
				display: flex;
				justify-content: center;
				align-items: center;
				line-height: 100rpx;
				text-align: center;
				.text {
					display: inline-block;
					transform: rotate(90deg);
				}
			}
		}
	}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值