js + canvas 签名插件

通过js + canvas实现签名效果

在init方法中构建canvas实例,并绑定相应的鼠标事件,通过单独的事件绑定方法,将三种鼠标事件处理逻辑分割开来。

init() {
    let canvas = document.querySelector(this._id);
    this.ctx = canvas.getContext('2d');
    this.canvas = canvas;
    this._width = canvas.width;
    this._height = canvas.height;
    this._bindMethod('mousedown', this._start);
    this._bindMethod('mousemove', this._move);
    this._bindMethod('mouseup', this._end);
}

鼠标按下时,获取当前的当前的坐标,并记录当前开始坐标,在此设置线样式。

_start({offsetX, offsetY}) {
      this._isStart = true;
      this.ctx.beginPath();
      this._startX = offsetX;
      this._startY = offsetY;
      this.ctx.moveTo(offsetX, offsetY);
      this._setLineStyle();
  }

鼠标移动时,记录每一次移动的坐标信息,也就是路径点位信息。

_move({offsetX, offsetY}) {
      if (!this._isStart) {
          return;
      }
      this.ctx.lineTo(offsetX, offsetY);
      this._endX = offsetX;
      this._endY = offsetY;
      this._currentPath.push([offsetX, offsetY]);
      this.ctx.stroke();
  }

移动结束后,记录这一次总体路径信息,重置相关属性。

this._isStart = false;
this.path.push({
    startX: this._startX,
    startY: this._startY,
    path: this._currentPath
});
this._clearData();

设置线宽,通过类型转化判断当前参数类型是否为数字类型或字符串数字。

setLineWidth(lineWidth = 1) {
    if (isNaN(+lineWidth)) {
        alert('数据类型不是数字类型或字符串数字');
        throw new Error('数据类型不是数字类型或字符串数字');
    }
    this._lineWidth = lineWidth;
}

设置线颜色。

setLineColor(lineColor = '#000') {
      this._lineColor = lineColor;
  }

撤回功能,根据路径记录信息,重新绘制图形。

withdraw() {
    let length = this.path.length;
    if (!length) {
        this.reset()
    }
    this._clearCanvas();
    this.path.splice(length - 1, 1);
    this.path.forEach(({startX, startY, path}) => {
        this.ctx.beginPath();
        this._setLineStyle();
        this.ctx.moveTo(startX, startY);
        path.forEach(([endX, endY]) => {
            this.ctx.lineTo(endX, endY);
        })
        this.ctx.stroke();
    });
}

下载功能。

downLoad() {
    let base64URL = this.canvas.toDataURL('image/png');
    const a = document.createElement('a');
    a.download = '签名';
    const mimeType = base64URL.match(/:(.*?);/)[1];
    const byteCharacters = atob(base64URL.split(',')[1]);
    const byteNumbers = new Array(byteCharacters.length);
    for (let i = 0; i < byteCharacters.length; i++) {
        byteNumbers[i] = byteCharacters.charCodeAt(i);
    }
    const byteArray = new Uint8Array(byteNumbers)
    const blob = new Blob([byteArray], { type: mimeType });
    a.href = URL.createObjectURL(blob);
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
}
注意

坐标选取处理(IE下没有pageX,pageY)
pageX,pageY:鼠标指针的位置,文档的坐标。(也就是相对于浏览器窗口),会受css样式偏移量的影响,如padding,margin等
offsetX,offsetY:鼠标相对于事件源元素的x,y坐标。
在这里插入图片描述

完整代码

sign.js插件代码

class SignCanvas {
    constructor(id) {
        this.canvas = null;
        this.ctx = null;
        this._id = id;
        this._width = 0;
        this._height = 0;
        this._startX = null;
        this._startY = null;
        this._endX = null;
        this._endY = null;
        this.path = [];
        this._currentPath = [];
        this._isStart = false;
        this._lineWidth = 1;
        this._lineColor = '#000';
    }

    init() {
        let canvas = document.querySelector(this._id);
        this.ctx = canvas.getContext('2d');
        this.canvas = canvas;
        this._width = canvas.width;
        this._height = canvas.height;
        this._bindMethod('mousedown', this._start);
        this._bindMethod('mousemove', this._move);
        this._bindMethod('mouseup', this._end);
    }

    _bindMethod(key, method) {
        this.canvas.addEventListener(key, (e) => {
            method.call(this, e);
        })
    }

    _start({offsetX, offsetY}) {
        this._isStart = true;
        this.ctx.beginPath();
        this._startX = offsetX;
        this._startY = offsetY;
        this.ctx.moveTo(offsetX, offsetY);
        this._setLineStyle();
    }

    _move({offsetX, offsetY}) {
        if (!this._isStart) {
            return;
        }
        this.ctx.lineTo(offsetX, offsetY);
        this._endX = offsetX;
        this._endY = offsetY;
        this._currentPath.push([offsetX, offsetY]);
        this.ctx.stroke();
    }

    _end() {
        this._isStart = false;
        this.path.push({
            startX: this._startX,
            startY: this._startY,
            path: this._currentPath
        });
        this._clearData();
    }

    _setLineStyle() {
        this.ctx.lineWidth = this._lineWidth;
        this.ctx.strokeStyle = this._lineColor;
    }

    _clearData() {
        this._startX = null;
        this._startY = null;
        this._endX = null;
        this._endY = null;
        this._currentPath = [];
    }

    _clearCanvas() {
        this.ctx.clearRect(0, 0, this._width, this._height);
    }

    setLineWidth(lineWidth = 1) {
        if (isNaN(+lineWidth)) {
            alert('数据类型不是数字类型或字符串数字');
            throw new Error('数据类型不是数字类型或字符串数字');
        }
        this._lineWidth = lineWidth;
    }

    setLineColor(lineColor = '#000') {
        this._lineColor = lineColor;
    }

    reset() {
        this._clearCanvas();
        this._clearData();
        this.path = [];
    }

    withdraw() {
        let length = this.path.length;
        if (!length) {
            this.reset()
        }
        this._clearCanvas();
        this.path.splice(length - 1, 1);
        this.path.forEach(({startX, startY, path}) => {
            this.ctx.beginPath();
            this._setLineStyle();
            this.ctx.moveTo(startX, startY);
            path.forEach(([endX, endY]) => {
                this.ctx.lineTo(endX, endY);
            })
            this.ctx.stroke();
        });
    }

    downLoad() {
        let base64URL = this.canvas.toDataURL('image/png');
        const a = document.createElement('a');
        a.download = '签名';
        const mimeType = base64URL.match(/:(.*?);/)[1];
        const byteCharacters = atob(base64URL.split(',')[1]);
        const byteNumbers = new Array(byteCharacters.length);
        for (let i = 0; i < byteCharacters.length; i++) {
            byteNumbers[i] = byteCharacters.charCodeAt(i);
        }
        const byteArray = new Uint8Array(byteNumbers)
        const blob = new Blob([byteArray], { type: mimeType });
        a.href = URL.createObjectURL(blob);
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }
}

export default SignCanvas;

html代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>canvas签字</title>
</head>
<style>
    html, body {
        /*padding: 0;*/
        /*margin: 0;*/
    }
</style>
<body>
<div>
    <button id="withdraw">撤回</button>
    <button id="downLoad">下载</button>
</div>
<canvas id="canvas" width="400" height="400" style="border: 1px solid #7d7676; margin-top: 10px">
    该浏览器不支持canvas标签
</canvas>

<script type="module">
    import SignCanvas from './sign.js';

    let sign = new SignCanvas('#canvas');
    sign.init();

    let btn = document.querySelector('#withdraw');
    let btnDown = document.querySelector('#downLoad');

    btn.addEventListener('click', () => {
        sign.withdraw();
    });

    btnDown.addEventListener('click', () => {
        sign.downLoad();
    })

</script>
</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值