canvas组件作为小程序的一大亮点,为开发者提供了丰富的绘图功能,本文探索如何借助canvas组件在微信小程序上轻松实现手写签名功能。效果图如下,附源码。
新建组件popup
popup.wxml代码
<!--components/popup/popup.wxml-->
<block wx:if="{{showModal}}">
<view class="modal-mask"></view>
<view class="modal-dialog">
<view class='m-title'>
{{title}}
<image catchtap="selectColorEvent" src="{{ selectColor === 'black' ? '../../assets/img/color_black_selected.png' : '../../assets/img/color_black.png' }}" class="{{ selectColor === 'black' ? 'color_select' : '' }} black-select" data-color="black"
data-color-value="#1A1A1A"></image>
<image catchtap="selectColorEvent" src="{{ selectColor === 'red' ? '../../assets/img/color_red_selected.png' : '../../assets/img/color_red.png' }}" class="{{ selectColor === 'red' ? 'color_select' : '' }} red-select" data-color="red"
data-color-value="#CA262A"></image>
<mp-icon catchtap="cleardraw" class="clear_img" icon="delete" color="#000000" size="16"></mp-icon>
</view>
<view class="m-info">
<canvas class='canvasView' id="myCanvas" canvas-id="myCanvas" disable-scroll="true" bindtouchstart="canvasStart" bindtouchmove="canvasMove" bindtouchend="canvasEnd" touchcancel="canvasEnd" binderror="canvasIdErrorCallback"></canvas>
<slot name="content">
</slot>
</view>
<view class="modal-footer">
<view class="btn-footer btn-l-footer" bindtap="closeDialog">
<text>{{cancelText}}</text>
</view>
<view class="btn-footer btn-r-footer" bindtap="saveCanvasAsPngImg">
<text>{{confirmText}}</text>
</view>
</view>
</view>
</block>
popup.wxss文件代码
page{
width:100%;
overflow-x:hidden;
}
/* 蒙层 */
.modal-mask {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
background: #000;
opacity: 0.5;
overflow: hidden;
z-index: 1000;
color: #fff;
}
/* 弹框 */
.modal-dialog{
font-size: 30rpx;
width:100%;
position: fixed;
bottom: 0;
left:0;
right: 0;
margin:auto;
z-index: 1500;
background: #fff;
border-radius: 8rpx;
display: flex;
flex-direction: column;
}
.m-title {
height: 80rpx;
line-height: 80rpx;
text-align: center;
align-self: stretch;
font-size: 36rpx;
}
.m-info{
padding-top: 10rpx;
font-size: 24rpx;
text-align: center;
color: #888;
/* flex: 1; */
}
.m-avatur{
width:120rpx;
height: 120rpx;
border: 1rpx solid #eee;
border-radius: 50%;
}
.m-name{
margin-top: 10rpx;
font-size: 30rpx;
}
.m-star{
display: flex;
width: 190rpx;
margin: 20rpx auto 0;
}
.m-phone{
margin-top:30rpx;
font-size: 30rpx;
color: #888888;
}
.modal-footer{
display: flex;
height: 100rpx;
border-top: 1rpx solid #E5E5E5;
}
.btn-footer{
width: 50%;
font-size: 36rpx;
color: #02BB00;
display: flex;
align-items: center;
justify-content: center;
}
.btn-l-footer{
border-right: 1rpx solid #E5E5E5;
}
.btn-l-footer{
border-right: 1rpx solid #E5E5E5;
}
.stars-box {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20rpx;
}
.star-icon {
display: block;
width: 80rpx;
height: 80rpx;
margin-right: 20rpx;
}
.little-star {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
.star-icon:last-child {
margin-right: 0;
}
.m-no-grade {
text-align: center;
color: #888888;
margin-top:10rpx;
}
.canvasView {
width: calc(100% - 10px);
height: 500rpx;
border: 1rpx solid #E5E5E5;
margin: 10px 10px 5px 5px ;
background:round
}
.black-select {
width: 40rpx;
height: 40rpx;
position: absolute;
top: 40rpx;
left: 25rpx;
}
.black-select.color_select {
width: 70rpx;
height: 70rpx;
top: 28rpx;
left: 10rpx;
}
.red-select {
width: 40rpx;
height: 40rpx;
position: absolute;
top: 40rpx;
left: 80rpx;
}
.red-select.color_select {
width: 70rpx;
height: 70rpx;
top: 28rpx;
left: 71rpx;
}
.clear_img {
width: 60rpx;
height: 40rpx;
position: absolute;
top: 20rpx;
left: 140rpx;
}
popup.js文件代码
var context = null;// 使用 wx.createContext 获取绘图上下文 context
var isButtonDown = false;
var arrx = [];
var arry = [];
var arrz = [];
var app = getApp();
var util = require("../../utils/util")
Component({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
/**
* 组件的属性列表
*/
properties: {
title: {
type: String,
value: '手写签名'
},
content: {
type: String,
value: ''
},
keyId:{
type:String,
value:''
},
customParam:{
type:Object,
value:{}
},
cancelText: {
type: String,
value: '取消'
},
confirmText: {
type: String,
value: '确定'
}
},
/**
* 组件的初始数据
*/
data: {
showModal: false, //控制手写签名组件显示与隐藏,
canvasWidth:0,
canvasHeight:0,
popupType:null, //打开手写板的作用 1代表个人中心上传自己的签名
selectColor:"black", //画笔颜色
lineColor:"#1A1A1A", //s线条颜色
lineWidth:5, //线条宽度
lineCap: "round", //线条绘制结束帽
lineJoin: "round"//两条线相交时的拐角类型
},
onReady: function (options) {
console.log(this.object);
},
/**
* 组件的方法列表
*/
methods: {
//隐藏
hide() {
this.cleardraw();
this.setData({
showModal: false
})
},
//显示
show(popupType) {
this.data.popupType = popupType;
this.setData({
showModal: true
})
},
//关闭弹窗
closeDialog: function () {
this.cleardraw();
this.hide();
this.triggerEvent('close', { close: true});
},
//笔迹开始
canvasStart: function (event) {
isButtonDown = true;
arrz.push(0);
arrx.push(event.changedTouches[0].x);
arry.push(event.changedTouches[0].y);
},
//笔记移动
canvasMove: function (event) {
if (event.type != 'touchmove') {
return false;
}
if (event.cancelable) {
// 判断默认行为是否已经被禁用
if (!event.defaultPrevented) {
event.preventDefault();
}
}
if (isButtonDown) {
arrz.push(1);
arrx.push(event.changedTouches[0].x);
arry.push(event.changedTouches[0].y);
};
for (var i = 0; i < arrx.length; i++) {
if (arrz[i] == 0) {
context.moveTo(arrx[i], arry[i])
} else {
context.lineTo(arrx[i], arry[i])
};
};
context.setLineWidth(this.data.lineWidth);
context.setStrokeStyle(this.data.lineColor);
context.stroke();
context.draw(false);
},
//笔迹结束
canvasEnd: function (event) {
isButtonDown = false;
},
//清除画布
cleardraw: function () {
//清除画布
arrx = [];
arry = [];
arrz = [];
//在给定矩形范围类清除像素
context.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
//清空上次的绘制
context.draw(true);
},
//初始化
setCtx:function(){
let query = wx.createSelectorQuery().in(this);
query.select('#myCanvas').boundingClientRect(rect => {
this.setData({
canvasWidth: rect.width,
canvasHeight: rect.height
})
}).exec();
context = wx.createCanvasContext("myCanvas", this);
this.setContextAttribute();
},
//设置context的属性
setContextAttribute:function(){
context.beginPath();
//在给定矩形范围类清除像素
context.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight);
//设置线条颜色
context.setStrokeStyle(this.data.lineColor);
//设置线条宽度
context.setLineWidth(this.data.lineWidth);
//线条绘制结束帽
context.setLineCap(this.data.lineCap);
//两条线相交时的拐角类型
context.setLineJoin('round');
},
//保存canvas为图片,格式png
saveCanvasAsPngImg:function() {
var that = this;
var userData = wx.getStorageSync("userData");
const eventChannel = this.getOpenerEventChannel();
wx.canvasToTempFilePath({
canvasId: 'myCanvas',
fileType: 'png',
quality: 1, //图片质量,0-1,超过为1.0处理
success(res) {
if(that.data.popupType == 1){
that.uploadFile(res, userData);
}else{
that.triggerEvent('myEvent', { filePath: res.tempFilePath});
that.hide();
}
},
fail(error){
wx.showToast({
title: '上传失败',
});
}
},this)
},
/**
* 画笔颜色改变
*/
selectColorEvent:function(event){
var color = event.currentTarget.dataset.colorValue;
var colorSelected = event.currentTarget.dataset.color;
this.setData({
selectColor: colorSelected,
lineColor: color
});
this.setContextAttribute();
this.cleardraw();
},
/**
* 上传签名至服务器
*/
uploadFile(res, userData){
let that = this;
wx.showLoading({
title: '上传中',
})
let url ='服务器地址';
util.uploadImage(url, res.tempFilePath).then(res => {
that.hide()
wx.hideLoading()
if(res.success){
let userData = wx.getStorageSync("userData");
userData.signaturePhoto = "已经有了手写签名";
wx.setStorageSync('userData', userData);
wx.showToast({
title: '上传成功',
})
}else{
util.errotInfoShow(res.message);
}
}).catch(err =>{
wx.hideLoading()
})
}
},
})
其中,用到的图片和icon地址需要换成你自己的,如果需要我的,可以留言发你。