JS案例:支持PC端和Mobile端的Canvas电子签名功能

简介: JS案例:支持PC端和Mobile端的Canvas电子签名功能

前言:

这段时间项目迭代时遇到了一个新需求,基于react实现一个Pc版电子签名功能,并生成图片上传。于是我想到了signature_pad,并且在项目使用了这个插件

不得不说,用别人造的轮子是真的香,出于好奇,想用原生实现一下电子签名的功能


以下是实现过程


HTML和css可以参照源码,这里不过多介绍


首先引入eventBus,方便代码解耦

然后实现Base基类存放公共方法和属性,后续有啥共用属性或方法可以往这加

//基类:公共方法和属性
import event from './eventBus.js'
export default class Base {
    constructor(canvasEle, dom = document) {
        this.event = event //注册发布订阅
        this.canvasEle = canvasEle //待操作的画布标签
        this.dom = dom //dom
        return this;
    }
}

完成之后,我们先实现Pc版的电子签名功能,新建一个PcPrint继承自Base,参照之前写的鼠标拖拽案例,实现在canvas上拖拽功能,并将事件结果的坐标发布出去。

其中clearDefaultEvent函数和getClient函数在Base类中实现

// PC端,鼠标事件
import Base from './base.js'
let that = null
export default class PcPrint extends Base {
    constructor(ele, dom) {
        super(ele, dom)
        that = this //注册全局this
        this.init()
        return this;
    }
    init() {
        that.canvasEle.addEventListener('mousedown', that.onMouseDown)
    }
    onMouseDown(e = event) {
        that.clearDefaultEvent(e)
        that.dom.addEventListener('mouseup', that.onMouseUp) //给dom添加mouseup避免产生鼠标点下时,移出画布造成其他的问题
        that.canvasEle.addEventListener('mousemove', that.onMouseMove)
        that.event.emitEvent('pointStart', that.getClient(e)) //触发开始签字事件
    }
    onMouseUp(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointEnd') //触发结束签字事件
        that.canvasEle.removeEventListener('mousemove', that.onMouseMove) //移除移动事件
    }
    onMouseMove(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointMove', that.getClient(e)) //触发签字事件
    }
}

Base类添加以下代码:

    /**
     * 取消默认事件和事件冒泡
     * @param e 事件对象
     */
    clearDefaultEvent(e) {
        e.preventDefault()
        e.stopPropagation()
    }
    /**
     * 获取事件元素离body可视区域的坐标
     * @param target 事件目标
     */
    getClient(target) {
        return {
            x: target.clientX,
            y: target.clientY
        }
    }

接着,我们对事件抛出的三个发布进行订阅,新建Print类,对获取的坐标通过canvas进行绘制

import Base from "./Base.js"
import PcPrint from './pc.js';
// import MobilePrint from './mobile.js';
let that = null
export default class Print extends Base {
    constructor(canvasEle, options, dom) {
        super(canvasEle, dom)
        that = this
        this.options = options //配置画笔颜色,粗细,是否开启移动端或PC端,
        this.init() //初始化属性,配置,注册发布订阅等
        this.initCanvas() //初始化画布
        return this
    }
    init() {
        //Pc和Mobile启用开关
        this.Pc = this.options.Pc ? (new PcPrint(this.canvasEle)) : null
        // this.Mobile = this.options.Mobile ? (new MobilePrint(this.canvasEle)) : null
        this.point = null //存储上一次坐标
        this.event.onEvent('pointMove', that.pointMove) //订阅签字事件
        this.event.onEvent('pointStart', that.pointStart) //订阅签字开始事件
        this.event.onEvent('pointEnd', that.pointEnd) //订阅签字结束事件
    }
    initCanvas() {
        this.clientRect = this.canvasEle.getBoundingClientRect() // 获取标签相对可视区域的偏移量
        this.canvasEle.width = this.canvasEle.parentNode.offsetWidth //设置为父元素的宽
        this.canvasEle.height = this.canvasEle.parentNode.offsetHeight //设置为父元素的高
        this.context = this.canvasEle.getContext('2d')
        this.context.strokeStyle = this.options.color; // 线条颜色
        this.context.lineWidth = this.options.weight; // 线条宽度
    }
    pointStart(point) {
        that.point = that.shiftingPosition(point, that.clientRect) //初始化起始位置
    }
    pointEnd() {
        that.point = null //清空起始位置
    }
    pointMove(point) {
        that.canvasDraw(that.shiftingPosition(point, that.clientRect)) //签字效果
    }
    canvasDraw(point) { //画布操作
        this.context.beginPath() //新建(重置)路径
        this.context.moveTo(this.point.x, this.point.y) //画布绘画起始点移动到前一个坐标
        this.context.lineTo(point.x, point.y) //画布从前一个坐标到当前坐标
        this.context.stroke() //从moveTo到lineTo进行绘制
        this.context.closePath() //创建从当前坐标回到前一个坐标的路径
        that.point = point //将此次坐标赋值给下一次移动时的前一个坐标
    }
}


考虑到canvas的偏移问题,在Base中添加shiftingPosition函数,解决画布绘制时坐标偏移问题


   /**
     * 抵消画布偏移
     * @param point 当前坐标
     * @param shift 偏移量
     */
    shiftingPosition(point, shift) {
        return {
            x: point.x - shift.left,
            y: point.y - shift.top
        }
    }

最后,在index中实例化电子签名

<script type="module">
    import Print from "./js/print.js"
    new Print(printBox,{
        Pc:true,
        Mobile:true,
        color:'lightcoral',
        weight:5
    })
</script>


效果如下:

1.gif


Pc端实现完成之后是Mobile端,代码大同小异,除了事件类型不用之外,还一点就是移动端的多指触碰支持,touchevent支持双指事件,此时我们要判断是否单指输入

// Mobile端,触摸事件
import Base from './base.js'
let that = null
export default class MobilePrint extends Base {
    constructor(ele, dom) {
        super(ele, dom)
        that = this //注册全局this
        this.init()
        return this;
    }
    init() {
        that.canvasEle.addEventListener('touchstart', that.onTouchStart)
    }
    onTouchStart(e = event) {
        that.clearDefaultEvent(e)
        that.canvasEle.addEventListener('touchend', that.onTouchEnd) //没有像pc一样给dom添加touchend,因为touchmove是基于touchstart和touchend之间触发的,只要touchend触发,touchmove便失效
        that.canvasEle.addEventListener('touchmove', that.onTouchMove)
        that.event.emitEvent('pointStart', that.getClient(e.touches[0])) //这里可以做一个判断e.touches是否只有一个(e.touches表示有几个手指触碰)
    }
    onTouchEnd(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointEnd')
        that.canvasEle.removeEventListener('touchmove', that.onTouchMove)
    }
    onTouchMove(e = event) {
        that.clearDefaultEvent(e)
        that.event.emitEvent('pointMove', that.getClient(e.touches[0]))
    }
}

在移动端实现的效果:

1.gif

最后:

附上源码地址:Gitee


相关文章
|
16天前
|
JavaScript 前端开发
js变量的作用域、作用域链、数据类型和转换应用案例
【4月更文挑战第27天】JavaScript 中变量有全局和局部作用域,全局变量在所有地方可访问,局部变量只限其定义的代码块。作用域链允许变量在当前块未定义时向上搜索父级作用域。语言支持多种数据类型,如字符串、数字、布尔值,可通过 `typeof` 检查类型。转换数据类型用 `parseInt` 或 `parseFloat`,将字符串转为数值。
18 1
|
1月前
|
JavaScript 前端开发
js实现点击音频实现播放功能
js实现点击音频实现播放功能
|
3天前
|
JavaScript 前端开发 测试技术
编写JavaScript模块化代码主要涉及将代码分割成不同的文件或模块,每个模块负责处理特定的功能或任务
【5月更文挑战第10天】编写JavaScript模块化代码最佳实践:使用ES6模块或CommonJS(Node.js),组织逻辑相关模块,避免全局变量,封装细节。利用命名空间和目录结构,借助Webpack处理浏览器环境的模块。编写文档和注释,编写单元测试以确保代码质量。通过这些方法提升代码的可读性和可维护性。
8 3
|
3天前
|
前端开发 JavaScript
JS长按保存canvas绘图
JS长按保存canvas绘图
7 0
|
3天前
|
JavaScript 前端开发
JavaScript实现识别二维码信息功能
JavaScript实现识别二维码信息功能
8 1
|
3天前
|
JavaScript
js实现全屏功能——易懂版
js实现全屏功能——易懂版
6 0
|
12天前
|
JavaScript 前端开发 API
如何利用JavaScript和Electron构建具有丰富功能的桌面应用
【4月更文挑战第30天】如何利用JavaScript和Electron构建具有丰富功能的桌面应用
5 0
|
13天前
|
JavaScript 前端开发
实现一个JavaScript动态日期功能
实现一个JavaScript动态日期功能
|
13天前
|
JavaScript 前端开发
JavaScript模糊搜索功能
JavaScript模糊搜索功能
|
13天前
|
JavaScript 前端开发
JavaScript三级联动功能
JavaScript三级联动功能