前端小工具:CanvasDraw签名工具

前端小工具:CanvasDraw签名工具

移动端签名提示函

1、安装

npm install react-canvas-draw --save or yarn add react-canvas-draw

 支持导出图片,文件数据流,

移动端横屏显示,

用于react项目

贴代码,大家都喜欢的。

import { Modal } from 'antd-mobile';
import PropTypes from 'prop-types';
import React, { PureComponent } from 'react';
import CanvasDraw from 'react-canvas-draw';
import style from './style';
class CanvasDraws extends PureComponent {
  constructor(props) {
    super(props);
    this.signatureRef = React.createRef();
    this.canvasRef = React.createRef();
    this.placeholderRef = React.createRef();
    this.state = {
      visible: false,
      imgUrl: '',
      // 是否已经签名
      isAutograph: false,
      // 比例是否旋转
      isScaleRotate: true,
      signatureWidth: 300,
      signatureHeight: 500,
    };
  }
  componentDidMount() {
    this.setWidthHeight();
    window.addEventListener('resize', () => {
      this.setWidthHeight();
      this.setPlaceholderView();
    });
  }
  // 设置宽高
  setWidthHeight = () => {
    const width = document.documentElement.clientWidth;
    const height = document.documentElement.clientHeight;
    // 如果宽度大于高度,则不旋转签名比例
    if (width > height) {
      this.setState({
        isScaleRotate: false,
        signatureWidth: width,
        signatureHeight: height - 64,
      });
    } else {
      this.setState({
        isScaleRotate: true,
        signatureWidth: width - 64,
        signatureHeight: height,
      });
    }
  };
  // 显示签名函
  showCanvasDraws = () => {
    this.setState({
      visible: true,
    });
  };
  // 关闭签名提示函
  handleCloseVisible = () => {
    this.setState({
      isAutograph: false,
      visible: false,
    });
  };
  handleClear = () => {
    this.setPlaceholderView();
    this.canvasRef.current.clear();
    this.setState({
      isAutograph: false,
    });
  };
  handleConfirm = async () => {
    const { onComplete } = this.props;
    // const imgUrl = this.canvasRef.current.canvas.drawing.toDataURL('image/png');
    this.canvasRef.current.canvas.drawing.toBlob(async blob => {
      const blobRotate = await this.rotateBlob(blob);
      const blobUrl = window.URL.createObjectURL(blobRotate);
      this.setState({
        imgUrl: blobUrl,
      });
      this.handleCloseVisible();
      onComplete(blobRotate);
    });
  };
  // 设置占位符显示隐藏功能
  setPlaceholderView(view = 'block') {
    if (this.placeholderRef.current && this.placeholderRef.current.style) {
      this.placeholderRef.current.style.display = view;
    }
  }
  handleIsSignState = () => {
    this.setPlaceholderView('none');
    this.setState({
      isAutograph: true,
    });
  };
  // 旋转图片
  rotateBlob = (data = '') => {
    const boldUrl = window.URL.createObjectURL(data);
    const { isScaleRotate } = this.state;
    //传入需要旋转的base64图片
    return new Promise(resolve => {
      const imgView = new Image();
      imgView.src = boldUrl;
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 };
      imgView.onload = () => {
        const imgW = imgView.width;
        const imgH = imgView.height;
        canvas.width = imgH;
        canvas.height = imgW;
        canvas.fillStyle = '#fff';
        let imgData = null;
        // 如果比例旋转变化,则旋转图片,否则默认返回
        if (isScaleRotate) {
          const size = imgH;
          cutCoor.sx = size;
          cutCoor.sy = size - imgW;
          cutCoor.ex = size + imgH;
          cutCoor.ey = size + imgW;
          context.translate(size, size);
          // 旋转图片
          context.rotate((Math.PI / 2) * 3);
          context.drawImage(imgView, 0, 0);
          imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
        } else {
          context.drawImage(imgView, 0, 0);
          imgData = context.getImageData(0, 0, imgW, imgH);
        }
        for (let i = 0; i < imgData.data.length; i += 4) {
          // 当该像素是透明的,则设置成白色
          if (imgData.data[i + 3] === 0) {
            imgData.data[i] = 255;
            imgData.data[i + 1] = 255;
            imgData.data[i + 2] = 255;
            imgData.data[i + 3] = 255;
          }
        }
        context.putImageData(imgData, 0, 0);
        canvas.toBlob(blob => {
          resolve(blob);
        }, 'image/jpeg');
      };
    });
  };
  // 签名模板
  renderSignatureRegion() {
    const { visible, isScaleRotate, signatureWidth, signatureHeight, isAutograph } = this.state;
    return (
      <style.CanvasDraw>
        <div className={isScaleRotate ? 'rotate' : 'normal'}>
          <div
            className="signature-regin"
            ref={this.signatureRef}
            style={{ width: signatureWidth }}
          >
            {visible && (
              <CanvasDraw
                ref={this.canvasRef}
                loadTimeOffset={5}
                brushRadius={2}
                lazyRadius={0}
                brushColor="#444"
                backgroundColor="#fff"
                catenaryColor="#0a0302"
                gridColor="transparent"
                hideInterface
                canvasWidth={signatureWidth}
                canvasHeight={signatureHeight}
                onChange={() => this.handleIsSignState()}
              />
            )}
            <div className="signature-placeholder" ref={this.placeholderRef}>
              <p>签名区域</p>
            </div>
            <button
              aria-label="Close"
              className="am-modal-close close-signature"
              onClick={this.handleCloseVisible}
            ></button>
          </div>
          <div className="signature-btn">
            <div className="signature-tips">
              <a>
                请保持手机<b>横屏</b>,并按照从左到右的方向居中签名
              </a>
            </div>
            <div className="signature-btn-container">
              <button onClick={() => this.handleClear()} className="clear-signature">
                清除签名
              </button>
              <button
                onClick={() => this.handleConfirm()}
                className={isAutograph ? 'submit-signature' : 'submit-signature disabled'}
                disabled={!isAutograph}
              >
                保存签名
              </button>
            </div>
          </div>
        </div>
      </style.CanvasDraw>
    );
  }
  render() {
    const { visible, imgUrl } = this.state;
    const { agreedraw } = this.props;
    return (
      <style.Signature>
        <div className="signature default">
          <div onClick={this.showCanvasDraws}>
            <div className="signature-block">
              {agreedraw ? (
                <img className="preview" src={imgUrl}></img>
              ) : (
                <div className="placeholder">
                  <p>点击此处签名</p>
                </div>
              )}
            </div>
          </div>
          {/* 签名区域 */}
          <Modal popup className="signature-region-wrap" animationType="slide-up" visible={visible}>
            {this.renderSignatureRegion()}
          </Modal>
        </div>
      </style.Signature>
    );
  }
}
// 类型检查
CanvasDraws.propTypes = {
  agreedraw: PropTypes.bool,
  onComplete: PropTypes.func,
};
// 默认值
CanvasDraws.defaultProps = {
  // 是否签名
  agreedraw: false,
  // 签名完成
  onComplete: () => {},
};

export default CanvasDraws;

css:只能参考哇

import { pxToRem as rem } from 'lib/style.lib';
import styled from 'styled-components';

export default {
  Signature: styled.div`
    .signature {
      position: relative;
      border-radius: 2px;
      text-align: center;
      height: ${rem(276)};

      background: #fff;
      display: flex;
      align-items: center;
      justify-content: center;
      i &.default {
        border-radius: ${rem(4)};
        box-shadow: 0 2px 20px 0 rgba(0, 0, 0, 0.05);
      }
      &.default {
        background: #eaebee;
      }
      &.result {
        .preview {
          max-height: ${rem(280)};
        }
        /* &:after {
          @include border2(full, #e0e0e0, rem(4px));
        } */
      }
      .signature-block {
        width: 100%;
      }
      .preview {
        display: block;
        width: 100%;
        height: ${rem(276)};
        box-sizing: border-box;
        border: 1px solid #eaebee;
      }
      .placeholder {
        color: #d1d1d1;
        i {
          font-size: ${rem(38)};
          svg {
            display: block;
            margin: 0 auto;
          }
        }
        p {
          font-size: ${rem(44)};
          line-height: 1.1;
          color: #949ca8;
        }
      }
      .tip-signature {
        line-height: 1.1;
        font-size: ${rem(28)};
      }
    }
  `,
  CanvasDraw: styled.div`
    // 弹出框
    .signature-regin {
      position: relative;
      left: 0;
    }
    .close-signature {
      position: absolute;
      right: 15px;
      top: 0;
      padding: 8px 0;
      width: 30px;
      height: 30px;
      color: #999;
      font-size: ${rem(40)};

      .am-modal-close-x {
        color: #999 !important;
        font-size: 18px !important;
        &:after {
          display: none !important;
        }
        svg {
          position: absolute;
          top: 50%;
          left: 50%;
          transform: translate(-50%, -50%);
        }
      }
    }

    .am-modal-close {
      top: 15px;
    }
    .signature-placeholder {
      position: absolute;
      top: 50%;
      left: -24px;
      width: 100%;
      transform: translateY(-55%);
      pointer-events: none;
      i {
        color: #d1d1d1;
        font-size: ${rem(56)};
      }
      p {
        margin-top: 5px;
        font-size: ${rem(140)};
        color: #e8eaed;
      }
    }

    .signature-btn {
      display: flex;
      background: rgba(13, 110, 255, 0.1);
      padding: 12px 32px 12px 24px;
      justify-content: space-between;
      align-items: center;

      a {
        font-size: 15px;
        color: #1272ff;
      }

      button {
        width: 80px;
        height: 38px;
        border: 1px solid rgba(13, 110, 255);
        border-radius: 19px;
      }
      button:nth-of-type(1) {
        background-color: rgba(90, 154, 255, 0.1);
        color: #1272ff;
        margin-right: 16px;
      }
      button:nth-of-type(2) {
        background-color: #1272ff;
        color: #fff;
        &.disabled {
          background-color: #93afd8;
        }
      }
    }
    .rotate {
      .signature-regin {
        left: 64px;
      }
      .signature-placeholder {
        transform: translateY(-55%) rotate(90deg);
      }
      .close-signature {
        top: auto !important;
        bottom: 15px !important;
      }
      .signature-btn {
        position: absolute;
        left: 0;
        top: 0;
        width: 64px;
        height: 100%;
        padding: 15px 10px;
        flex-direction: column;
        background: rgba(13, 110, 255, 0.1);

        .signature-tips {
          display: flex;
          transform: rotate(90deg) translateX(50%);
          width: 500px;
        }

        .signature-btn-container {
          padding-right: ${rem(60)};
          display: flex;
          transform: rotate(90deg) translateX(-50%);
          button {
            position: relative;
          }
        }
      }
    }
  `,
};

我没有才华,只能是贴代码了。
大家加油了。

posted @ 2022-04-11 09:25  smallbore  阅读(353)  评论(0编辑  收藏  举报
回顶部