taro实现微信小程序手写电子签名功能

背景:

之所以写这个功能,是因为手写签名应该是canvas功能点最少的一个组件了吧,在微信小程序手动实现一波,有利于后面对小程序canvas的入门,先看效果.

效果:

未签名状态

随意签名状态

实现:

技术点1:

为了保证用户的最大化签名区域,采用了横屏的形式.

小程序并非微信小游戏,没有横屏的概念,那就只好自己手动横过来了, 签名区域与交互区域都横屏了,实现难点主要在css上,实现css如下:

.canvas-box {
  width: calc(100vw - 120px);
  height: 100vh;
  margin-left: 120px;
  position: relative;
  .text_warp{
    .text-detail{
      width: 300px;
      top: 50%;
      left: 50%;
      transform: translate(-50%,-50%) rotate(90deg);
      position: absolute;
      font-size: 50px;
      color: #333;
      white-space: nowrap;
    }
  }
}

这里没做旋转,只是把高宽控制了一下.

技术点2:

左方的文字区域则做了相应的旋转:

.layout-flex{
  display: flex;
  justify-content: space-between;
  position: absolute;
  top: 0;
  left: 100px;
  width: 100vh;
  transform: rotate(90deg);
  transform-origin:left top;
  height: 120px;
  .read{
    display: flex;
    line-height: 100px;
    font-size: 20px;
    .at-checkbox{
      &:before{
        display: none;
      }
      &:after{
        display: none;
      }
    }
    .at-checkbox__option-wrap{
      padding-right: 0;
    }
  }
  .btn-warp{
    display: flex;
    .at-button + .at-button{
      margin: 0 10px;
    }
  }
}
旋转的时候记得配置一下origin,旋转原点,默认是中间的.


技术点3:js部分的代码
import Taro, {Component, Config} from '@tarojs/taro';
import {View, Button, Canvas, Text} from '@tarojs/components';
import {APP_URLS} from '@/utils/api/api_urls'
import {upload} from '@/utils/api/httpRequest'
import './index.scss'
import {
  AtButton,
  AtCheckbox
} from 'taro-ui';

let ctx = Taro.createCanvasContext('canvas', this);
let startX = 0;
let startY = 0;
let canvasw = 0;
let canvash = 0;


export default class Signature extends Component {

  config = {
    navigationBarTitleText: '电子签名'
  }
  state = {
    isPaint: false,
    tempFilePath: '',
    checkedList: ['list1'],
    isShow: true, // 是否展示文字说明
    trustorIndex: 0,
  }

  initCanvas() {
    ctx = Taro.createCanvasContext('canvas', this);
    ctx.setStrokeStyle('#000000');
    ctx.setLineWidth(4);
    ctx.setLineCap('round');
    ctx.setLineJoin('round');
  }

  canvasStart = (e) => {
    if (startX !== 0) {
      this.setState({
        isShow: false
      })
    }
    startX = e.changedTouches[0].x
    startY = e.changedTouches[0].y
    ctx.beginPath()
  }

  canvasMove = (e) => {
    if (startX !== 0) {
      this.setState({
        isPaint: true,
        isShow: false
      })
    }
    let x = e.changedTouches[0].x
    let y = e.changedTouches[0].y
    ctx.moveTo(startX, startY)
    ctx.lineTo(x, y)
    ctx.stroke();
    ctx.draw(true)
    startX = x
    startY = y
  }

  canvasEnd(e) {
    console.log('结束')
  }

  clearDraw = () => {
    startX = 0;
    startY = 0;
    ctx.clearRect(0, 0, canvasw, canvash);
    ctx.draw(true);
    this.setState({
      isPaint: false,
      tempFilePath: ''
    })
  }

  createImg() {
    if (!this.state.isPaint) {
      Taro.showToast({
        title: '签名内容不能为空!',
        icon: 'none'
      });
      return false;
    }
    // 生成图片
    Taro.canvasToTempFilePath({
      canvasId: 'canvas',
      success: async res => {
         console.log(res)
      },
      fail(err) {
        console.log(err)
      }
    })
  }

  uploadImage(filePath) {
    return new Promise((resolve, reject) => {
      let data = {
        url: APP_URLS.UPLOAD_FILES,
        filePath,
        name: 'file',
        formDate: {}
      }

      upload(data)
        .then(res => {
          console.log(res);
          resolve(res);
        })
        .catch(e => {
          Taro.showToast({title: '材料上传失败,请稍后再试', icon: 'none'})
        })
    })
  }

  // 获取 canvas 的尺寸(宽高)
  getCanvasSize() {
    const query = Taro.createSelectorQuery()
    query.select('#canvas').boundingClientRect(function (res) {
      canvasw = res.width
      canvash = res.height
    })
    query.exec()
  }

  // 重新签名
  afreshDraw = () => {
    this.setState({
      canSign: true
    })
  }

  componentDidMount() {
    this.getCanvasSize()
    this.initCanvas()
  }

  componentWillUnmount() {
    ctx = null
  }

  handleChange = (checkedList) => {
    this.setState({
      checkedList
    })
  }

  handleHideWarp = () => {
    this.setState({
      isShow: false
    })
  }

  render() {
    return (
      <View className="signature">
        <View className="canvas-box">
          <Canvas
            id="canvas"
            canvasId="canvas"
            className="canvas"
            disableScroll={true}
            onTouchStart={this.canvasStart.bind(this)}
            onTouchMove={this.canvasMove.bind(this)}
            onTouchEnd={this.canvasEnd.bind(this)}
            onTouchCancel={this.canvasEnd.bind(this)}
            onClick={this.handleHideWarp}
            disable-scroll={true}
          >
          </Canvas>
          {
            this.state.isShow &&
            <View className='text_warp' onTouchStart={this.handleHideWarp} onClick={this.handleHideWarp}>
              <Text
                className='text-detail'>请在此区域签名</Text>
            </View>
          }
        </View>

        <View className="layout-flex buttons">
          <View className='read'>
            <AtCheckbox
              options={[
                {
                  value: 'list1',
                  label: '',
                }
              ]}
              selectedList={this.state.checkedList}
              onChange={this.handleChange.bind(this)}
            />
            <Text>我已阅读并同意《电子签名协议》</Text>
          </View>
          <View className='btn-warp'>
            <AtButton onClick={this.clearDraw}>重新签名</AtButton>
            <AtButton customStyle={{margin: '0 10px'}} type='primary'
                      onClick={this.createImg.bind(this)}>确认签名</AtButton>
          </View>
        </View>

      </View>
    );
  }
}

全部copy到微信小程序里即可正常使用.


排坑:
坑1:
旋转的问题,有时候把握不好或者说空间想像能力不够丰富,导致旋转的时候花费了一些时间.
坑2:
上面的placeholder的问题.有想过直接在canvas里绘制出这几个文字,但是会引起其它的问题,比如要保证文字的垂直居中状态,这个时候就得计算文字的长度,
然后绘制到canvas上,这显然有些麻烦. 所以换了另外一种思路,直接一个div覆盖在最上方,开始绘制的时候给隐藏了,这种方式可能不适合所有项目.
坑3:
微信小程序在开发的时候使用真机预览的电子签名绘制的时候会有所卡顿,也不知道大家是否都一样,但是一旦上传到体验版本又是比较流畅的,这个无从去排查问题.


总结:
    多尝试项目,可以让自己更好的成长

 

Author: kaykie

发表评论

邮箱地址不会被公开。