微信小程序——canvas轻松实现手写签名弹窗组件

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地址需要换成你自己的,如果需要我的,可以留言发你。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值