likes
comments
collection
share

使用react+canvas封装一个小而美的手写签名组件

作者站长头像
站长
· 阅读数 62

成品展示

使用react+canvas封装一个小而美的手写签名组件

组件使用调用方式

// 引入组件
import Signature from "components/Signature";
// 调用组件
<SignatureBtn
  width={"1030px"}
  btnText="手签"
  onConfirm={this.handleSignature}
></SignatureBtn>

实现思路

  1. 我使用的技术栈是React同时使用的是类组件,面向对象编程。
  2. 考虑到需要控制鼠标在容器里写字,所以需要使用canvas技术。
  3. 具体实现逻辑: a. 页面有一个"手签的按钮",点击按钮显示一个弹出框,点击确认或取消关闭弹出框。 b. 弹出框内展示手写签名的组件。 c. 签名组件包括:签字,清空重新,生成图片等操作。 d. 核心签名逻辑即:控制鼠标的 mousedownmouseupmousemove事件。
  4. 组件的拆分: 根据功能拆为两个部分。 外层组件:一个按钮和一个弹出框,通过按钮控制弹出框的显示及隐藏。 内层组件:手写签字核心部分。 a. 签字逻辑。 b. 清空逻辑。 c. 回退上一步逻辑。 d. canvas 转换为 base64 字符串逻辑。 e. ocr识别。 这部分后续增加。

核心代码示例

// 手写签字核心类
class Signature extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isDrawing: false,
      drawingSteps: [], // 绘图步骤; 用于回退功能
      lastX: 0,
      lastY: 0,
      width: 990,
      height: 300,
      dataURL: "", //生成的图片的Base64字符串
    };
  }
  canvasRef = React.createRef();

  componentDidMount() {
    const canvas = this.canvasRef.current;
    const context = canvas.getContext("2d");
    context.strokeStyle = "blue";
    context.lineWidth = 2;
    
    // 注册事件
    canvas.addEventListener("mousedown", this.handleMouseDown);
    canvas.addEventListener("mouseup", this.handleMouseUp);
    canvas.addEventListener("mousemove", this.handleMouseMove);
  }
  componentWillUnmount() {
    const canvas = this.canvasRef.current;
    // 销毁事件
    canvas.removeEventListener("mousedown", this.handleMouseDown);
    canvas.removeEventListener("mouseup", this.handleMouseUp);
    canvas.removeEventListener("mousemove", this.handleMouseMove);
  }
  //鼠标左键按下
  handleMouseDown = (e) => {
    this.setState({
      isDrawing: true,
      lastX: e.offsetX,
      lastY: e.offsetY,
    });
  };
  // 鼠标左键释放
  handleMouseUp = () => {
    this.setState({
      isDrawing: false,
    });
  };
  // 鼠标按下拖动
  handleMouseMove = (e) => {
    let { isDrawing, lastX, lastY, drawingSteps } = this.state;
    if (!isDrawing) return;
    const canvas = this.canvasRef.current;
    const context = canvas.getContext("2d");
    const newX = e.offsetX;
    const newY = e.offsetY;
    context.beginPath();
    context.moveTo(lastX, lastY);
    context.lineTo(newX, newY);
    context.stroke();
    this.setState({
      lastX: newX,
      lastY: newY,
      drawingSteps: [...drawingSteps, { lastX, lastY, newX, newY }],
    });
  };
  // canvas 转换为 base64字符串
  generateImage = () => {
    const canvas = this.canvasRef.current;
    const dataURL = canvas.toDataURL();
    debugger;
    this.setState({
      dataURL,
    });
    return dataURL;
  };
  // 清空操作
  clearAll = () => {
    const canvas = this.canvasRef.current;
    const context = canvas.getContext("2d");
    context.clearRect(0, 0, 990, 300);
    this.setState({
      dataURL: "",
      drawingSteps: [],
    });
  };
  // 回退操作
  undoStep = () => {
    let { drawingSteps } = this.state;
    const canvas = this.canvasRef.current;
    const context = canvas.getContext("2d");
    if (drawingSteps.length > 0) {
      const lastStep = drawingSteps[drawingSteps.length - 40];
      context.clearRect(0, 0, 990, 300);
      drawingSteps.slice(0, -1).forEach((step) => {
        context.beginPath();
        context.moveTo(step.lastX, step.lastY);
        context.lineTo(step.newX, step.newY);
        context.stroke();
      });
      this.setState({
        drawingSteps: drawingSteps.slice(0, -1),
      });
    }
  };

  render() {
    let { showBtn } = this.props;
    let { dataURL, width, height } = this.state;
    return (
      <div>
        <canvas
          ref={this.canvasRef}
          width={width}
          height={height}
          style={signtureStyle}
        ></canvas>
        {!!dataURL && showImg && (
          <img
            style={previewStyle}
            src={dataURL}
            width={width / 2}
            height={height / 2}
          ></img>
        )}
        {showBtn && (
          <>
            <button onClick={this.undoStep}>上一步</button>
            <button onClick={this.clearAll}>重置</button>
            <button onClick={this.generateImage}>生成图片</button>
          </>
        )}
      </div>
    );
  }
}

思考

  1. 封装组件需要考虑独立性,可移植性,尽量减少对其他代码的依赖。
  2. 封装的可扩展性,一个组件不可能一次写好,适用所有场景,需要不断的改善,兼容不同场景。
  3. 逻辑封层,能拆分则拆分。这样利于扩展,代码可读性也强。
转载自:https://juejin.cn/post/7257882929718837309
评论
请登录