프론트엔드에서 전자서명 공용화 실현(웹, 모바일단말기)

너겟 성장 여정을 시작하세요! "너겟 데일리 신규플랜·12월 업데이트 챌린지" 참여 15일차 입니다. 이벤트 자세히 보기 클릭

머리말

현재 시대의 발전에 따라 이전의 자필 서명에서 점차 전자 서명이 파생되고 있습니다. 전자 서명은 종이 자필 서명과 동일한 법적 효력을 갖습니다. 현재 전자서명은 본인확인이 필요한 상품링크와 사법관련 상품에 주로 활용되고 있다.

일반적인 예를 들자면 누구나 DingTalk를 사용해 보았고 DingTalk에는 전자 서명이 있으므로 모두 알고 있어야 한다고 생각합니다.

그렇다면 전자 서명을 프런트 엔드로 구현하려면 어떻게 해야 할까요? 사실 html5중요한 수준의 보조 태그가 에 등장했습니다. 무엇입니까? 그것은 캔버스 입니다 .

무엇인가요canvas

Canvas(画布)웹 페이지에서 실시간으로 이미지를 생성하는 데 사용되는 의 새로운 태그 로 HTML5이미지 콘텐츠를 조작할 수 있으며 기본적으로 사용 가능한 작업 JavaScript입니다 位图(bitmap). CanvasElement를Canvas 나타내는 객체 - . HTML자체 동작은 없지만 스크립팅된 클라이언트 측 그리기 작업을 지원하는 API를 정의합니다.

모국어는 canvas그 위에 그릴 수 있는 레이블 이고 javaScript그것이 제공하는 것을 통해 그려지며 그 과정에서 캔버스 역할을 합니다 .context(上下文)Apicanvas

<canvas></canvas>
复制代码

사용하는 방법

canvasApi그것은 우리 가 사용할 수 있는 많은 것을 제공합니다 . 레이블에 레이블을 body생성하고 , 레이블에서 이 레이블의 노드를 가져 와서 사용하기 위해 생성하기만 하면 됩니다.canvasscriptcanvascontext(上下文)

...
<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,现在我们的电子签名功能还差一丢丢可以实现完了,现在已经可以正常的签名了。

我们来看一下效果:

291891492291361822022-12-07-11-13-41.gif

取消功能/清空画布

我们在刚开始创建的那两个按钮开始排上用场了。

这里我们创建一个cancel的方法作为取消并清空画布使用

    // 取消-清空画布
    const cancel = () => {
        // 清空当前画布上的所有绘制内容
        ctx.clearRect(0, 0, config.width, config.height)
    }
复制代码

然后我们将这个方法和取消按钮进行绑定

     <button onclick="cancel()">取消</button>
复制代码

保存功能

这里我们创建一个save的方法作为保存画布上的内容使用。

캔버스에 내용을 저장하는 방법은 여러 가지가 있는데 이 두 가지 해결 방법이 图片/文件더 일반적 이지만 이 버디가 약하고 적응력이 좋지 않습니다. 그래서 여기에서는 레이블➕ 체계를 사용하여 사진을 저장하고 다운로드합니다.blobtoDataURLtoDataURLblobablob

    // 保存-将画布内容保存为图片
    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>
复制代码

우리는 방금 그린 내용을 저장하고 저장 버튼을 클릭하면 다운로드되어 저장됩니다

이미지.png

전체 코드

<!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>. OS X 대시보드 및 Safari용으로 Apple에서 처음 도입했습니다. Internet Explorer는 IE9부터 지원되어 왔으며 <canvas> 이전 버전의 IE에서는  Google의 Explorer Canvas 프로젝트  의 스크립트를 도입하여 페이지를 지원할 수 있습니다 <canvas>. Google 크롬 및 Opera 9+도 지원됩니다  <canvas>.

애플릿에서 프롬프트

스몰 프로그램에서 구현해야 한다면 같은 원리인데 创建实例和上下文수정 필요 합니다 .Apidomdom操作dom

рекомендация

отjuejin.im/post/7174251833773752350