Vue2,实现电子签名(web、移动端)功能

这篇具有很好参考价值的文章主要介绍了Vue2,实现电子签名(web、移动端)功能。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Vue2,实现电子签名(web、移动端)功能

一、简述

现如今,电子签名与手写签名一样具有法律效应。越来越多的项目开发中会有电子签名的需求,自己最近的项目也会频繁出现该需求。一般开发时会用到现有的npm依赖包vue-signature-pad,但是自己所处的开发环境不能连接外网,所以打算自己研究和总结实现电子签名功能。

实现电子签名功能,需要用到html5中一个重要级别的辅助标签——canvas

二、canvas介绍

什么是canvas

HTML5<canvas>用于图形的绘制。它只是一个图形容器,不提供任何绘制对象的信息。画布的内容并不像html那样具有语义并能暴露出来。

它的图形绘制,通常是使用javascript来完成的,可以通过多种方法来绘制路径、盒、圆、字符以及添加图像等。

如何实现canvas

  1. 创建canvas元素
  2. 获取canvas元素
  3. 创建context对象

Vue2(2.6.11)

<template>
	<div class="ml_sign">
        <!-- 创建canvas元素(标签) -->
        <canvas ref="signature" id="signature"></canvas>
    </div>
</template>

<script>
export default {
    name: 'Signatrue',
    data () {
        return {
            ctx: null
        }
    },
    mounted () {
        // 获取canvas实例
        const canvas = this.$refs.signature
        // 创建context对象
        this.ctx = canvas.getContext('2d')
    }
}
</script>

canvas给我们提供了很多的Api,供我们使用。

getContext('2d')对象是内建的HTML5对象,拥有多种绘制路径、矩形、圆形、字符、以及添加图像的方法。

在这里需要先添加两个按钮,分别是取消和保存,后续会用到。

<canvas ref="signature" id="signature"></canvas>
<div class="btn-wrapper">
    <button>取消</button>
    <button>保存</button>
</div>

三、签名实现

实现步骤:

  1. 配置基础内容
  2. 获取canvas实例
  3. 基础内容设置
  4. 设备兼容 - 绑定事件
  5. 开始绘制
  6. 绘制
  7. 结束绘制
  8. 取消功能/清空画布
  9. 保存功能 - 图片显示、本地下载、上传后端存储
1.配置基础内容
  • 定义宽、高、线条颜色、线条宽度等基础内容;
<script>
export default {
    ...,
    data () {
        return {
              canvas: null, // 存储canvas节点
              ctx: null, // 存储canvas的context上下文
              config: {
                width: 400, // 宽度
                height: 200, // 高度
                strokeStyle: 'red', // 线条颜色
                lineWidth: 4, // 线条宽度
                lineCap: 'round', // 设置线条两端圆角
                lineJoin: 'round' // 线条交汇处圆角
              },
              client: {
                offsetX: 0, // 偏移量
                offsetY: 0,
                endX: 0, // 坐标
                endY: 0
              },
              points: [] // 记录坐标 用来判断是否有签名的
        }
    }
}
</script>
2.获取canvas实例
<script>
export default {
    ...,
    // 注意:习惯使用created生命周期的童鞋,将无法获取到canvas节点。
    mounted () {
        // 初始化
        this.init()
    },
    methods: {
        // 初始化
        init () {
            const canvas = this.$refs.signature
            // 存储canvas节点
            this.canvas = canvas
            // 创建context对象
            this.ctx = canvas.getContext('2d')
        }
    }
}
</script>
3.基础内容设置
  • 设置canvas的宽、高等基础配置;
  • 注意:这里需要注意的是,canvas的默认宽高是 width: 300 height: 150,若是style设置width和height,可能会出现拉伸问题。所以尽量使用canvas内置属性设置width和height,不然会有bug。
<script>
export default {
    ...,
    methods: {
    	// 初始化
        init () {
              const canvas = this.$refs.signature
              canvas.width = this.config.width // 设置canvas的宽
              canvas.height = this.config.height // 设置canvas的高
              // 设置一个边框
              canvas.style.border = '1px solid #000'

              // 存储canvas节点
              this.canvas = canvas
              // 创建context对象
              this.ctx = canvas.getContext('2d')

              // 设置相应配置
              this.ctx.fillStyle = 'transparent'
              this.ctx.lineWidth = this.config.lineWidth
              this.ctx.strokeStyle = this.config.strokeStyle
              this.ctx.lineCap = this.config.lineCap
              this.ctx.lineJoin = this.config.lineJoin

              // 绘制填充矩形
              this.ctx.fillRect(
                0, // x 轴起始绘制位置
                0, // y 轴起始绘制位置
                this.config.width, // 宽度
                this.config.height // 高度
              )
        }
    }
}
</script>
4.设备兼容 - 绑定事件
  • 定义计算属性,判断是否为移动端;
  • 监听canvas 鼠标/手势按下 和 鼠标/手势 弹起/离开 事件;
<script>
export default {
    ...,
    computed: {
    	// 判断是否为移动端
        mobileStatus () {
          return (/Mobile|Android|iPhone/i.test(navigator.userAgent))
        }
	},
    methods: {
        // 初始化
        init () {
              ...
              
              // 创建鼠标/手势按下监听器
              canvas.addEventListener(this.mobileStatus ? 'touchstart' : 'mousedown', this.startDraw)
              // 创建鼠标/手势 弹起/离开 监听器
              canvas.addEventListener(this.mobileStatus ? 'touchend' : 'mouseup', this.cloaseDraw)
        }
    }
}
</script>
5.开始绘制
  • 鼠标/手势按下后,获取偏移量及坐标并存储;
  • 清除以上一次 beginPath 之后的所有路径,进行绘制;
  • moveTo设置画线起始点位;
  • 监听 鼠标移动或手势移动;
<script>
export default {
    ...,
    methods: {
        // 初始化
        init () { ... },
        // 开始绘制
        startDraw (event) {
              // 获取偏移量及坐标
              const { offsetX, offsetY, pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event

              // 修改上次的偏移量及坐标
              this.client.offsetX = offsetX
              this.client.offsetY = offsetY
              this.client.endX = pageX
              this.client.endY = pageY

              // 清除以上一次 beginPath 之后的所有路径,进行绘制
              this.ctx.beginPath()
              // 设置画线起始点位
              this.ctx.moveTo(this.client.endX, this.client.endY)
              // 监听 鼠标移动或手势移动
              this.canvas.addEventListener(this.mobileStatus ? 'touchmove' : 'mousemove', this.draw)
        }
    }
}
</script>
6.绘制
  • 获取当前坐标点位;
  • lineTo根据坐标点位移动添加线条;
  • stroke绘制;
  • 记录坐标;
<script>
export default {
    ...,
    methods: {
        // 初始化
        init () { ... },
        // 开始绘制
        startDraw () { ... },
        // 绘制
        draw (event) {
              // 获取当前坐标点位
              const { pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
              // 修改最后一次绘制的坐标点
              this.client.endX = pageX
              this.client.endY = pageY
              const obj = {
                x: pageX,
                y: pageY
              }

              // 根据坐标点位移动添加线条
              this.ctx.lineTo(pageX, pageY)

              // 绘制
              this.ctx.stroke()

              // 记录坐标
              this.points.push(obj)
        }
    }
}
</script>
7.结束绘制
  • closePath结束绘制;
  • 移除 鼠标移动或手势移动 监听器;
<script>
export default {
    ...,
    methods: {
        // 初始化
        init () { ... },
        // 开始绘制
        startDraw () { ... },
        // 绘制
        draw () { ... },
        // 结束绘制
        cloaseDraw () {
              // 结束绘制
              this.ctx.closePath()
              // 移除鼠标移动或手势移动监听器
              this.canvas.removeEventListener('mousemove', this.draw)
        }
    }
}
</script>
8.取消功能/清空画布
  • 绑定 取消功能/清空画布 事件;
  • 清空当前画布上的所有绘制内容;
  • 清空坐标;
<div class="btn-wrapper">
    <!-- 添加点击事件 -->
    <button @click="clear">取消</button>
    <button>保存</button>
</div>
<script>
export default {
    ...,
    methods: {
        // 初始化
        init () { ... },
        // 开始绘制
        startDraw () { ... },
        // 绘制
        draw () { ... },
        // 结束绘制
        cloaseDraw () { ... },
        // 取消/清空画布
        clear () {
              // 清空当前画布上的所有绘制内容
              this.ctx.clearRect(0, 0, this.config.width, this.config.height)
              // 清空坐标
              this.points = []
        }
    }
}
</script>
9.保存功能 - 图片显示、本地下载、上传后端存储

保存功能做了三个项目业务场景常用的方法,供大家参考或使用

图片显示:将签名转成base64,并放在img路径上,进行签名的图片展示;

本地下载:将签名转成blob流,并下载至本地(默认png格式图片);

上传后端存储:将签名转成base64,然后将base64转成File文件对象,再上传后端;

基础保存设置
  • 绑定保存事件;
  • 签名判空;
  • 操作事件;
<div class="btn-wrapper">
    <!-- 添加点击事件 -->
    <button @click="clear">取消</button>
    <button @click="save">保存</button>
</div>
<script>
export default {
    ...,
    methods: {
        // 初始化
        init () { ... },
        // 开始绘制
        startDraw () { ... },
        // 绘制
        draw () { ... },
        // 结束绘制
        cloaseDraw () { ... },
        // 取消/清空画布
        clear () { ... },
        // 保存
        save () {
              // 判断至少有20个坐标 才算有签名
              if (this.points.length < 20) {
                alert('签名不能为空!')
                return
              }
			
              // 操作事件
              ...
        }
    }
}
</script>
显示图片
  • 创建img标签,并绑定路径;
  • 定义操作事件;
  • canvas内容转成base64,并赋值img绑定路径;
<template>
	<div class="ml_sign">
        ...
        <!-- 创建img标签, 绑定路径 -->
        <img :src="imgurl">
    </div>
</template>

<script>
export default {
    ...,
    data () {
        ...,
        imgurl: '' // img图片路径
    },
    methods: {
        ...,
        // 保存
        save () {
              // 判断至少有20个坐标 才算有签名
              if (this.points.length < 20) {
                alert('签名不能为空!')
                return
              }
			
              // 操作事件
              this.dataToImg()
        },
        // img显示签名
        dataToImg () {
          // 转成base64
          const baseFile = this.canvas.toDataURL() // 默认转成png格式的图片编码
          this.imgurl = baseFile
        }
    }
}
</script>

vue2 移动端手写签字组件,vue.js,JavaScript,前端,javascript,vue.js

本地下载
  • 定义操作事件;
  • canvas内容转成blob流;
  • 通过a标签进行下载;
<script>
export default {
    ...,
    methods: {
        ...,
        // 保存
        save () {
              // 判断至少有20个坐标 才算有签名
              if (this.points.length < 20) {
                alert('签名不能为空!')
                return
              }
			
              // 操作事件
              this.dataUrlToPng()
        },
        // 将签名生成png图片
        dataUrlToPng () {
              // 将canvas内容转成blob流
              this.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>

vue2 移动端手写签字组件,vue.js,JavaScript,前端,javascript,vue.js文章来源地址https://www.toymoban.com/news/detail-825130.html

上传后端存储
  • canvas内容转成base64,自定义文件名;
  • base64转成File文件对象;
  • 上传签名;
<script>
export default {
    ...,
    methods: {
        ...,
        // 保存
        save () {
              // 判断至少有20个坐标 才算有签名
              if (this.points.length < 20) {
                alert('签名不能为空!')
                return
              }
			
              // 操作事件
              const baseFile = this.canvas.toDataURL() // 转成base64,默认转成png格式的图片编码
              const filename = `${Date.now()}.png` // 文件名字
              const file = this.dataURLToFile(baseFile, filename) // 图片文件形式 传给后端存储即可
              
              this.uploadSignatrue(file)
        },
        // 将base64转成File文件对象
        dataURLToFile (dataURL, filename) {
              const arr = dataURL.split(',')
              // 获取图片格式
              const imgType = arr[0].match(/:(.*?);/)[1]
              // atob() 方法用于解码使用 base-64 编码的字符串
              const dec = atob(arr[1])
              let n = dec.length
              const u8arr = new Uint8Array(n)
              while (n--) {
                    // 转成ASCII码
                    u8arr[n] = dec.charCodeAt(n)
              }
              return new File([u8arr], filename, { type: imgType })
        },
        // 上传签名
        uploadSignatrue (file) {
          const formData = new FormData()
          formData.append('file', file)
          formData.append('paramsOne', paramsOne)
          ...

          // 上传接口 这里就不赘述了
          uploadFile(formData, ...)
        },
    }
}
</script>

四、完整代码

<template>
  <div class="ml_sign">
      <canvas ref="signature" id="signature"></canvas>
      <div class="btn-wrapper">
        <button @click="clear">取消</button>
        <button @click="save">保存</button>
      </div>
      <img :src="imgurl">
  </div>
</template>

<script>
export default {
  name: 'Signatrue',
  data () {
    return {
      canvas: null, // 存储canvas节点
      ctx: null, // 存储canvas的context上下文
      config: {
        width: 400, // 宽度
        height: 200, // 高度
        strokeStyle: 'red', // 线条颜色
        lineWidth: 4, // 线条宽度
        lineCap: 'round', // 设置线条两端圆角
        lineJoin: 'round' // 线条交汇处圆角
      },
      points: [], // 记录坐标 用来判断是否有签名的
      client: {
        offsetX: 0, // 偏移量
        offsetY: 0,
        endX: 0, // 坐标
        endY: 0
      },
      imgurl: ''
    }
  },
  computed: {
    // 判断是否为移动端
    mobileStatus () {
      return (/Mobile|Android|iPhone/i.test(navigator.userAgent))
    }
  },
  mounted () {
    this.init()
  },
  methods: {
    // 初始化
    init () {
      const canvas = this.$refs.signature
      canvas.width = this.config.width // 设置canvas的宽
      canvas.height = this.config.height // 设置canvas的高
      // 设置一个边框
      canvas.style.border = '1px solid #000'

      // 存储canvas节点
      this.canvas = canvas
      // 创建context对象
      this.ctx = canvas.getContext('2d')

      // 设置相应配置
      this.ctx.fillStyle = 'transparent'
      this.ctx.lineWidth = this.config.lineWidth
      this.ctx.strokeStyle = this.config.strokeStyle
      this.ctx.lineCap = this.config.lineCap
      this.ctx.lineJoin = this.config.lineJoin

      // 绘制填充矩形
      this.ctx.fillRect(
        0, // x 轴起始绘制位置
        0, // y 轴起始绘制位置
        this.config.width, // 宽度
        this.config.height // 高度
      )

      // 创建鼠标/手势按下监听器
      canvas.addEventListener(this.mobileStatus ? 'touchstart' : 'mousedown', this.startDraw)
      // 创建鼠标/手势 弹起/离开 监听器
      canvas.addEventListener(this.mobileStatus ? 'touchend' : 'mouseup', this.cloaseDraw)
    },
    // 开始绘制
    startDraw (event) {
      // 获取偏移量及坐标
      const { offsetX, offsetY, pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event

      // 修改上次的偏移量及坐标
      this.client.offsetX = offsetX
      this.client.offsetY = offsetY
      this.client.endX = pageX
      this.client.endY = pageY

      // 清除以上一次 beginPath 之后的所有路径,进行绘制
      this.ctx.beginPath()
      // 设置画线起始点位
      this.ctx.moveTo(this.client.endX, this.client.endY)
      // 监听 鼠标移动或手势移动
      this.canvas.addEventListener(this.mobileStatus ? 'touchmove' : 'mousemove', this.draw)
    },
    // 绘制
    draw (event) {
      // 获取当前坐标点位
      const { pageX, pageY } = this.mobileStatus ? event.changedTouches[0] : event
      // 修改最后一次绘制的坐标点
      this.client.endX = pageX
      this.client.endY = pageY
      const obj = {
        x: pageX,
        y: pageY
      }

      // 根据坐标点位移动添加线条
      this.ctx.lineTo(pageX, pageY)

      // 绘制
      this.ctx.stroke()

      // 记录坐标
      this.points.push(obj)
    },
    // 结束绘制
    cloaseDraw () {
      // 结束绘制
      this.ctx.closePath()
      // 移除鼠标移动或手势移动监听器
      this.canvas.removeEventListener('mousemove', this.draw)
    },
    // 取消/清空画布
    clear () {
      // 清空当前画布上的所有绘制内容
      this.ctx.clearRect(0, 0, this.config.width, this.config.height)
      // 清空坐标
      this.points = []
    },
    // 保存
    save () {
      // 判断至少有20个坐标 才算有签名
      if (this.points.length < 20) {
        alert('签名不能为空!')
        return
      }

      // 操作事件
      const baseFile = this.canvas.toDataURL() // 转成base64,默认转成png格式的图片编码
      const filename = `${Date.now()}.png` // 文件名字
      const file = this.dataURLToFile(baseFile, filename) // 图片文件形式 传给后端存储即可

      this.uploadSignatrue(file)

      // this.dataUrlToPng()

      // this.dataToImg()
    },
    // img显示签名
    dataToImg () {
      // 转成base64
      const baseFile = this.canvas.toDataURL() // 默认转成png格式的图片编码
      this.imgurl = baseFile
    },
    // 将签名生成png图片
    dataUrlToPng () {
      // 将canvas上的内容转成blob流
      this.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()
      })
    },
    // 将base64转成File文件对象
    dataURLToFile (dataURL, filename) {
      const arr = dataURL.split(',')
      // 获取图片格式
      const imgType = arr[0].match(/:(.*?);/)[1]
      // atob() 方法用于解码使用 base-64 编码的字符串
      const dec = atob(arr[1])
      let n = dec.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        // 转成ASCII码
        u8arr[n] = dec.charCodeAt(n)
      }
      return new File([u8arr], filename, { type: imgType })
    },
    // 上传签名
    uploadSignatrue (file) {
      const formData = new FormData()
      formData.append('file', file)
      // formData.append('paramsOne', paramsOne)
      // ...
      console.log(formData)

      // 上传接口 这里就不赘述了
      // uploadFile(params, ...)
    }
  }
}
</script>

到了这里,关于Vue2,实现电子签名(web、移动端)功能的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue2+antd——实现动态菜单路由功能——基础积累

    最近在写后台管理系统,遇到一个需求就是要将之前的静态路由改为动态路由,使用的后台框架是: vue-antd-admin 然后通过 loadRoutes 方法来实现异步动态路由。 如上图所示,需要在登录接口调用成功后,书写以下的代码: import { loadRoutes } from \\\'@/utils/routerUtil.js\\\'; import { getCodeL

    2024年02月08日
    浏览(42)
  • SpringBoot和Vue2集成WebSocket,实现聊天室功能

    springboot集成websocket实现聊天室的功能。如有不足之处,还望大家斧正。

    2024年01月23日
    浏览(47)
  • vue2 实现后台管理系统左侧菜单联动实现 tab根据路由切换联动内容,并支持移动端框架

    效果图: pc端  移动端    由于代码比较多,我这里就不一一介绍了,可以去我的git上把项目拉下来 git地址https://gitee.com/Flechazo7/htglck.git 后台我是用node写的有需要的可以评论联系

    2024年02月16日
    浏览(46)
  • vue2 - 基于Element UI实现上传Excel表单数据功能

    批量数据上传后台,需要从后台下载一个固定格式的 Excel表格,然后在表格里面添加数据,将数据格式化,再上传给后台,后台做解析处理,往数据库添加数据 点击导入excel按钮,跳转到上传excel功能页面,点击上传或者是通过拖拽都能实现excel表格上传 通过Element UI的 el-di

    2024年02月13日
    浏览(38)
  • vue2实现二进制流pdf浏览器预览功能

    该方法不需要使用插件  获取后端二进制文件流后直接处理 然后点击调用方法使用

    2024年01月20日
    浏览(64)
  • Vue2 集成 CodeMirror 实现公式编辑、块状文本编辑,TAG标签功能

    效果图  安装codemirror依赖 本示例为Vue2项目,安装低版本的依赖 实现 实现代码如下,里边涉及到的变量和函数自行替换即可,没有其他复杂逻辑。

    2024年02月10日
    浏览(37)
  • 【h5+微信小程序】vue2实现h5扫码登录功能

    需要实现在同域名的h5页面上增加一个微信扫码登录的功能,如果用户已经有小程序的账号,可以直接登录。 使用 :vue2+微信小程序原生开发 可以实现上述功能的 前提 是:同一用户,对同一个微信开放平台下的不同应用,UnionID是相同的。域名已经配置。 可以用什么来区分

    2024年02月14日
    浏览(71)
  • vue2和vue3拖拽移动div

    直接上代码,代码可以直接运行, vue2拖拽移动div: vue3拖拽移动div: 设置div居中后,发现一开始拖拽时,div会跑到最左边,vue3优化代码如下:

    2024年02月07日
    浏览(40)
  • 前端实现电子签名(web、移动端)通用

    开启生长之旅!这是我参加「日新方案 12 月更文挑战」的第15天,点击检查活动概况 前语 在现在的年代发展中,从以前的手写签名,逐步衍生出了电子签名。电子签名和纸质手写签名一样具有法令效应。电子签名现在主要还是在需求个人确认的产品环节和司法类相关的产品

    2024年02月16日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包