开启生长之旅!这是我参加「日新方案 12 月更文挑战」的第15天,点击检查活动概况

前语

在现在的年代发展中,从以前的手写签名,逐步衍生出了电子签名。电子签名和纸质手写签名一样具有法令效应。电子签名现在主要还是在需求个人确认的产品环节和司法类相关的产品上较多。

举个常用的例子,咱们都用过钉钉,钉钉上面就有电子签名,信任咱们这肯定是知道的。

那作为前端的咱们怎么完成电子签名呢?其实在html5中现已呈现了一个重要级别的辅佐标签,是啥呢?那就是canvas。

什么是canvas

Canvas(画布)是在HTML5中新增的标签用于在网页实时生成图画,并且能够操作图画内容,基本上它是一个能够用JavaScript操作的位图(bitmap)Canvas 目标表明一个 HTML 画布元素 -。它没有自己的行为,但是界说了一个 API 支撑脚本化客户端绘图操作。

大白话就是canvas是一个能够在上面经过javaScript画图的标签,经过其供给的context(上下文)Api进行制作,在这个过程中canvas充任画布的角色。

<canvas></canvas>

怎么运用

canvas给咱们供给了许多的Api,供咱们运用,咱们只需求在body标签中创立一个canvas标签,在script标签中拿到canvas这个标签的节点,并创立context(上下文)就能够运用了。

...
<body>
    <canvas></canvas>
</body>
<script>
    // 获取canvas 实例
    const canvas = document.querySelector('canvas')
    canvas.getContext('2d')
</script>
...

步入正题。

完成电子签名

知道几许的朋友都很清楚,线有点绘成,面由线绘成。

多点成线,多线成面。

所以咱们实践只需求拿到当时接触的坐标点,进行成线处理就能够了。

body中增加canvas标签

在这儿咱们不仅需求在在body中增加canvas标签,咱们还需求增加两个按钮,分别是撤销保存(后面咱们会用到)。

<body>
    <canvas></canvas>
    <div>
        <button>撤销</button>
        <button>保存</button>
    </div>
</body>

增加文件

我这儿全程运用js进行款式设置及增加。

// 装备内容
    const config = {
        width: 400, // 宽度
        height: 200, // 高度
        lineWidth: 5, // 线宽
        strokeStyle: 'red', // 线条色彩
        lineCap: 'round', // 设置线条两头圆角
        lineJoin: 'round', // 线条交汇处圆角
    }

获取canvas实例

这儿咱们运用querySelector获取canvas的dom实例,并设置款式和创立上下文。

    // 获取canvas 实例
    const canvas = document.querySelector('canvas')
    // 设置宽高
    canvas.width = config.width
    canvas.height = config.height
    // 设置一个边框,方便咱们检查及运用
    canvas.style.border = '1px solid #000'
    // 创立上下文
    const ctx = canvas.getContext('2d')

基础设置

咱们将canvas的填充色为透明,并制作填充一个矩形,作为咱们的画布,假如不设置这个填充背景色,在咱们初识渲染的时候是一个黑色背景,这也是它的一个默认色。

    // 设置填充背景色
    ctx.fillStyle = 'transparent'
    // 制作填充矩形
    ctx.fillRect(
        0, // x 轴开端制作方位
        0, // y 轴开端制作方位
        config.width, // 宽度
        config.height // 高度
    );

前次制作途径保存

这儿咱们需求声明一个目标,用来记录咱们上一次制作的途径完毕坐标点及偏移量。

  • 保存前次坐标点这个我不用说咱们都懂;
  • 为啥需求保存偏移量呢,由于鼠标和画布上的间隔是存在必定的偏移间隔,在咱们制作的过程中需求减去这个偏移量,才是咱们实践的制作坐标。
  • 但我发现chrome中不需求减去这个偏移量,拿到的就是实践的坐标,之前在微信小程序中运用就需求减去偏移量,需求在小程序中运用的朋友需求注意这一点哦。
    // 保存前次制作的 坐标及偏移量
    const client = {
        offsetX: 0, // 偏移量
        offsetY: 0,
        endX: 0, // 坐标
        endY: 0
    }

设备兼容

咱们需求它不仅能够在web端运用,还需求在移动端运用,咱们需求给它做设备兼容处理。咱们经过调用navigator.userAgent获取当时设备信息,进行正则匹配判别。

    // 判别是否为移动端
    const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))

初始化

这儿咱们在监听鼠标按下(mousedown)(web端)/接触开端(touchstart)的时候进行初始化,事情监听采用addEventListener

    // 创立鼠标/手势按下监听器
    window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)

三元判别阐明: 这儿当mobileStatustrue时则表明为移动端,反之则为web端,后续运用到的三元依旧是这个意思。

声明初始化办法

咱们增加一个init办法作为监听鼠标按下/接触开端的回调办法。

这儿咱们需求获取到当时鼠标按下/接触开端的偏移量和坐标,进行开端点制作。

Tips:web端能够直接经过event中取到,而移动端则需求在event.changedTouches[0]中取到。

这儿咱们在初始化后再监听鼠标的移动。

    // 初始化
    const init = event => {
        // 获取偏移量及坐标
        const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event 
        // 修正前次的偏移量及坐标
        client.offsetX = offsetX
        client.offsetY = offsetY
        client.endX = pageX
        client.endY = pageY
        // 铲除以上一次 beginPath 之后的一切途径,进行制作
        ctx.beginPath()
        // 依据装备文件设置进行相应装备
        ctx.lineWidth = config.lineWidth
        ctx.strokeStyle = config.strokeStyle
        ctx.lineCap = config.lineCap
        ctx.lineJoin = config.lineJoin
        // 设置画线开端点位
        ctx.moveTo(client.endX, client.endY)
        // 监听 鼠标移动或手势移动
        window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
    }

制作

这儿咱们增加制作draw办法,作为监听鼠标移动/接触移动的回调办法。

    // 制作
    const draw = event => {
        // 获取当时坐标点位
        const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
        // 修正最终一次制作的坐标点
        client.endX = pageX
        client.endY = pageY
        // 依据坐标点位移动增加线条
        ctx.lineTo(pageX , pageY )
        // 制作
        ctx.stroke()
    }

完毕制作

增加了监听鼠标移动/接触移动咱们必定要记住撤销监听并完毕制作,不然的话它会一直监听并制作的。

这儿咱们创立一个cloaseDraw办法作为鼠标弹起/完毕接触的回调办法来完毕制作并移除鼠标移动/接触移动的监听。

canvas完毕制作则需求调用closePath()让其完毕制作

    // 完毕制作
    const cloaseDraw = () => {
        // 完毕制作
        ctx.closePath()
        // 移除鼠标移动或手势移动监听器
        window.removeEventListener("mousemove", draw)
    }

增加完毕回调监听器

    // 创立鼠标/手势 弹起/离开 监听器
    window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)

ok,现在咱们的电子签名功能还差一丢丢能够完成完了,现在现已能够正常的签名了。

咱们来看一下作用:

撤销功能/清空画布

咱们在刚开端创立的那两个按钮开端排上用场了。

这儿咱们创立一个cancel的办法作为撤销并清空画布运用

    // 撤销-清空画布
    const cancel = () => {
        // 清空当时画布上的一切制作内容
        ctx.clearRect(0, 0, config.width, config.height)
    }

然后咱们将这个办法和撤销按钮进行绑定

     <button onclick="cancel()">撤销</button>

保存功能

这儿咱们创立一个save的办法作为保存画布上的内容运用。

将画布上的内容保存为图片/文件的办法有许多,比较常见的是blobtoDataURL这两种方案,但toDataURL这哥们没blob强,适配也不咋滴。所以咱们这儿采用a标签 ➕ blob方案完成图片的保存下载。

    // 保存-将画布内容保存为图片
    const save = () => {
        // 将canvas上的内容转成blob流
        canvas.toBlob(blob => {
            // 获取当时时间并转成字符串,用来作为文件名
            const date = Date.now().toString()
            // 创立一个 a 标签
            const a = document.createElement('a')
            // 设置 a 标签的下载文件名
            a.download = `${date}.png`
            // 设置 a 标签的跳转途径为 文件流地址
            a.href = URL.createObjectURL(blob)
            // 手动触发 a 标签的点击事情
            a.click()
            // 移除 a 标签
            a.remove()
        })
    }

然后咱们将这个办法和保存按钮进行绑定

    <button onclick="save()">保存</button>

咱们将刚刚制作的内容进行保存,点击保存按钮,就会进行下载保存

前端完成电子签名(web、移动端)通用

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <canvas></canvas>
    <div>
        <button onclick="cancel()">撤销</button>
        <button onclick="save()">保存</button>
    </div>
</body>
<script>
    // 装备内容
    const config = {
        width: 400, // 宽度
        height: 200, // 高度
        lineWidth: 5, // 线宽
        strokeStyle: 'red', // 线条色彩
        lineCap: 'round', // 设置线条两头圆角
        lineJoin: 'round', // 线条交汇处圆角
    }
    // 获取canvas 实例
    const canvas = document.querySelector('canvas')
    // 设置宽高
    canvas.width = config.width
    canvas.height = config.height
    // 设置一个边框
    canvas.style.border = '1px solid #000'
    // 创立上下文
    const ctx = canvas.getContext('2d')
    // 设置填充背景色
    ctx.fillStyle = 'transparent'
    // 制作填充矩形
    ctx.fillRect(
        0, // x 轴开端制作方位
        0, // y 轴开端制作方位
        config.width, // 宽度
        config.height // 高度
    );
    // 保存前次制作的 坐标及偏移量
    const client = {
        offsetX: 0, // 偏移量
        offsetY: 0,
        endX: 0, // 坐标
        endY: 0
    }
    // 判别是否为移动端
    const mobileStatus = (/Mobile|Android|iPhone/i.test(navigator.userAgent))
    // 初始化
    const init = event => {
        // 获取偏移量及坐标
        const { offsetX, offsetY, pageX, pageY } = mobileStatus ? event.changedTouches[0] : event 
        // 修正前次的偏移量及坐标
        client.offsetX = offsetX
        client.offsetY = offsetY
        client.endX = pageX
        client.endY = pageY
        // 铲除以上一次 beginPath 之后的一切途径,进行制作
        ctx.beginPath()
        // 依据装备文件设置相应装备
        ctx.lineWidth = config.lineWidth
        ctx.strokeStyle = config.strokeStyle
        ctx.lineCap = config.lineCap
        ctx.lineJoin = config.lineJoin
        // 设置画线开端点位
        ctx.moveTo(client.endX, client.endY)
        // 监听 鼠标移动或手势移动
        window.addEventListener(mobileStatus ? "touchmove" : "mousemove", draw)
    }
    // 制作
    const draw = event => {
        // 获取当时坐标点位
        const { pageX, pageY } = mobileStatus ? event.changedTouches[0] : event
        // 修正最终一次制作的坐标点
        client.endX = pageX
        client.endY = pageY
        // 依据坐标点位移动增加线条
        ctx.lineTo(pageX , pageY )
        // 制作
        ctx.stroke()
    }
    // 完毕制作
    const cloaseDraw = () => {
        // 完毕制作
        ctx.closePath()
        // 移除鼠标移动或手势移动监听器
        window.removeEventListener("mousemove", draw)
    }
    // 创立鼠标/手势按下监听器
    window.addEventListener(mobileStatus ? "touchstart" : "mousedown", init)
    // 创立鼠标/手势 弹起/离开 监听器
    window.addEventListener(mobileStatus ? "touchend" :"mouseup", cloaseDraw)
    // 撤销-清空画布
    const cancel = () => {
        // 清空当时画布上的一切制作内容
        ctx.clearRect(0, 0, config.width, config.height)
    }
    // 保存-将画布内容保存为图片
    const save = () => {
        // 将canvas上的内容转成blob流
        canvas.toBlob(blob => {
            // 获取当时时间并转成字符串,用来作为文件名
            const date = Date.now().toString()
            // 创立一个 a 标签
            const a = document.createElement('a')
            // 设置 a 标签的下载文件名
            a.download = `${date}.png`
            // 设置 a 标签的跳转途径为 文件流地址
            a.href = URL.createObjectURL(blob)
            // 手动触发 a 标签的点击事情
            a.click()
            // 移除 a 标签
            a.remove()
        })
    }
</script>
</html>

各内核和浏览器支撑情况

Mozilla 程序从 Gecko 1.8 (Firefox 1.5(en-US)) 开端支撑<canvas>。它首先是由 Apple 引进的,用于 OS X Dashboard 和 Safari。Internet Explorer 从 IE9 开端支撑<canvas>,更旧版本的 IE 中,页面能够经过引进 Google 的Explorer Canvas项目中的脚本来获得<canvas>支撑。Google Chrome 和 Opera 9+ 也支撑<canvas>

小程序中提示

在小程序中咱们假如需呀完成的话,也是同样的原理哦,只是咱们需求将创立实例和上下文Api进行修正,由于小程序中是没有dom,既然没有dom,哪来的操作dom这个操作呢。

  • 假如是uni-app则需求运用uni.createCanvasContext进行上下文创立

  • 假如是原生微信小程序则运用wx.createCanvasContext进行创立(2.9.0)之后的库不支撑