前端手写电子签名板实现方案

4 篇文章 0 订阅
1 篇文章 0 订阅

前端手写电子签名板实现

作者:@ 很菜的小白在分享
时间:2022年12月29日



示例图

介绍

什么是电子签名

电子签名是指数据电文中以电子形式所含、所附用于识别签名人身份并表明签名人认可其中内容的数据。—— 百度百科

通俗点说其实就是通过在电子设备上进行类似纸面上签字的效果。在如今互联网快速发展的时代,可以实现云签名,在线签订合同等各种场景。


初衷

这个功能是我在日常开发项目时遇到的需求场景,背景是公司开发的在线教育客户端,允许学生在客户端进行学习和答题,题目类型中有一项“解答题”,需要学生通过手写答题的方式进行作答。因为是第一次接触在线教育类型项目和这样的需求,所以产生了一些思考。

由于项目是使用uniApp开发的Pad客户端,在实现上与原生js有所区别,所以本文将通过原生js的方式重新实现一遍,并且让组件更加灵活通用。


思考

在刚接触到这个需求时,有的同学表示懵逼3连,这怎么实现?其实你只要相信,只要产品能提出的需求,世面上存在这样的功能,不管是用什么语言实现的,排除一些特殊功能,js大部分都可以实现。<canvas> 标签会在页面上创建一块画布允许我们在上面绘制各种图形,下面介绍实现该功能的主要技术点,如果对 Canvas 技术了解的可以直接跳过介绍部分。


什么是 canvas

Canvas API 提供了一个通过JavaScript 和 HTML的<canvas>元素来绘制图形的方式。它可以用于动画、游戏画面、数据可视化、图片编辑以及实时视频处理等方面。 —— MDN

举个例子:比如我们要实现一个长、宽各为50px的正方形。

正常操作:

  <div style="width: 50px; height: 50px; background-color: #000;"></div>

Canvas方式:

  <canvas id="myCanvas" width="200px" height="200px"></canvas>
  <script>
    const canvas = document.getElementById('canvas')
    const ctx = canvas.getContext("2d")

    ctx.fillStyle = "#000"
    ctx.fillRect(0, 0, 50, 50)
  </script>

实现效果

虽然这个例子中 Canvas 的实现方式更为复杂,那是因为 Canvas 在某些效果方面确实优于 html + css的方式,比如你需要绘制 sinx 的曲线,使用html+css方式就没有那么容易了,相反 Canvas可以轻松实现。

下面介绍一些常用到的 Canvas API。


Canvas 常用API
属性名说明返回值
width<canvas>元素的width属性,以CSS像素表示,未指定或者值为无效值(例如负数),则默认为300<canvas>元素的宽度
height<canvas>元素的height属性,以CSS像素表示,未指定或者值为无效值(例如负数),则默认为150<canvas>元素的高度
Canvas 常用方法
方法名说明参数返回值
captureStream()返回 CanvasCaptureMediaStream ,它是对画布表面的实时视频捕获。CanvasCaptureMediaStream
getContext()返回画布上的绘图上下文;如果不支持上下文 ID,则返回 null。2d webgl webgl2 bitmaprendererCanvasContext
toDataURL()返回由类型参数指定的格式的图像数据URL。参考type encoderOptionsData Url
toBlob()创建一个Blob 对象,表示 canvas 中包含的图像;该文件可以由用户代理决定是否缓存在磁盘上或存储在内存中。参考callback type type quality
CanvasRenderingContext2D 常用API
属性名说明
canvas获取上下文关联的Canvas元素
fillStyle描述颜色和样式的属性。默认值是 #000 (黑色)。
font描述绘制文字时,当前字体样式的属性。
lineCap指定如何绘制每一条线段末端的属性。有 3 个可能的值,分别是:butt, round and square。默认值是 butt。
lineWidth设置线段厚度的属性(即线段的宽度)。
strokeStyle描述画笔(绘制图形)颜色或者样式的属性。默认值是 #000 (black)。
textAlign描述绘制文本时,文本的对齐方式的属性。
textAlign描述绘制文本时,文本的对齐方式的属性。
CanvasRenderingContext2D 常用方法

参考

方法名说明
arc()绘制圆弧路径的方法。
beginPath()通过清空子路径列表开始一个新路径的方法。当你想创建一个新的路径时,调用此方法。
clearRect()通过把像素设置为透明以达到擦除一个矩形区域的目的。
drawImage()提供了多种在画布(Canvas)上绘制图像的方式。
getImageData()返回一个ImageData对象,用来描述 canvas 区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。
lineTo()使用直线连接子路径的终点到 x,y 坐标的方法(并不会真正地绘制)。
moveTo()将一个新的子路径的起始点移动到 (x,y) 坐标的方法。
measureText()返回一个关于被测量文本TextMetrics 对象包含的信息(例如它的宽度)。
save()通过将当前状态放入栈中,保存 canvas 全部状态的方法。
stroke()使用非零环绕规则,根据当前的画线样式,绘制当前或已经存在的路径的方法。

由于文章内容有限所以只列举常用 API 及 方法,想了解更多可以前往 MDN 了解更多 Canvas 相关知识。


实现

实现思路

实现思路大致就是,通过当前鼠标移动的位置在 Canvas 画布上绘制出鼠标的移动轨迹点,然后利用 Canvas 提供的 API 将点连成线来实现手写签名的过程。


具体实现

  /**
   * @class
   * @classdesc 实现手写签名的构造函数
   * @param {Object} 手写签名画板的配置项
   */
  function DrawingBoard(options) {
    if (!(this instanceof DrawingBoard)) {
      throw new TypeError("DrawingBoard constructor cannot be invoked without 'new'")
    }
    this.options = options
    this.canvas = null
    this.ctx = null
    this.drawStatus = false

    const _this = this

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标按下事件
     */
    DrawingBoard.prototype.touchStartPC = function (event) {
      _this.drawStatus = true
      const {offsetX, offsetY} = event
      _this.createBrush(offsetX, offsetY)
    }

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标移动事件
     */
    DrawingBoard.prototype.touchMovePC = function (event) {
      if (!_this.drawStatus) return;
      const {offsetX, offsetY} = event
      _this.drawPixel({offsetX, offsetY})
    }

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标抬起事件
     */
    DrawingBoard.prototype.touchEndPC = function (event) {
      _this.drawStatus = false
    }

    this.initSignatureCanvas()
  }

  /**
   * 初始化画板
   */
  DrawingBoard.prototype.initSignatureCanvas = function () {
    let canvas = document.createElement('canvas')
    canvas.setAttribute('width', '200px')
    canvas.setAttribute('height', '200px')
    this.canvas = canvas
    this.bindEvent(canvas)
    this.ctx = canvas.getContext('2d')
    this.options.el?.appendChild(canvas)
  }

  /**
   * @method
   * @param {Number} x 画笔的 x 坐标
   * @param {Number} y 画笔的 y 坐标
   * @desc 移动画笔创建连接点
   */
  DrawingBoard.prototype.createBrush = function (x, y) {
    this.ctx.beginPath()
    this.ctx.moveTo(x, y)
  }

  /**
   * @method
   * @param {Object} options 绘制线条的 x,y 坐标
   * @desc 根据点坐标绘制连线
   */
  DrawingBoard.prototype.drawPixel = function (options) {
    const {offsetX, offsetY} = options

    this.ctx.lineTo(offsetX, offsetY)
    this.ctx.fill()
    this.ctx.stroke();
    this.createBrush(offsetX, offsetY)
  }

  /**
   * @method
   * @desc 事件绑定
   */
  DrawingBoard.prototype.bindEvent = function () {
    this.canvas.addEventListener("mousedown", this.touchStartPC)
    this.canvas.addEventListener("mousemove", this.touchMovePC)
    this.canvas.addEventListener("mouseup", this.touchEndPC)
  }

示例

  <style>
    #drawingBoard {
      position: absolute;
      top: 200px;
      left: 200px;
      width: 200px;
      border: 2px solid #000;
      border-radius: 4px;
      overflow: hidden;
    }
  </style>

  <div id="drawingBoard"></div>
  var board = DrawingBoard({
    el: document.getElementById('drawingBoard'),
  })

效果

实现效果

扩展

自定义画布尺寸

initSignatureCanvas方法调整,新增 addAttribute 为画布添加属性样式。

  /**
   * 初始化画板
   */
  DrawingBoard.prototype.initSignatureCanvas = function () {
    let canvas = document.createElement('canvas')
    this.canvas = canvas

    // 新增
    this.addAttribute(canvas)

    this.bindEvent(canvas)
    this.ctx = canvas.getContext('2d')
    this.options.el?.appendChild(canvas)
  }

  /**
   * @method
   * @desc 为容器和画布添加属性样式
   */
  DrawingBoard.prototype.addAttribute = function () {
    let { width, height, style } = this.options
    const container = this.getClientRect()
    let styleParse = ''

    if(!width) width = '200px'
    if(!height) height = '200px'

    for (const key in style) {
      if (Object.prototype.hasOwnProperty.call(style, key)) {
        styleParse += `${toSplitLine(key)}: ${style[key]};`
      }
    }

    this.canvas.setAttribute('width', width)
    this.canvas.setAttribute('height', height)
    this.canvas.setAttribute('style', styleParse)

    if (this.options.el) { 
      if(container.width <= 0) this.options.el.style.width = width
      if (container.height <= 0) this.options.el.style.height = height
      if (this.options.brush && this.options.brush.pointer) {
        this.options.el.style.cursor = `url(${this.options.brush.pointer}) 0 16, default`
      }
    }
  }

options 配置项增加 width height 属性,允许传入画布尺寸信息,例:‘200px’。当未设置 #drawingBoard 容器宽高时,使用 options.width,默认:200px

示例
  var board = new DrawingBoard({
    el: document.getElementById('drawingBoard'),
    width: '200px',
    height: '200px'
  })

画板的禁用&启用

disable enable 用于切换画板是否启用。

  /**
   * @method
   * @desc 禁用画布
   */
  DrawingBoard.prototype.disable = function () {
    this.isDisable = true
    this.unBindEvent()
  }

  /**
   * @method
   * @desc 启用画布
   */
  DrawingBoard.prototype.enable = function () {
    this.isDisable = false
    this.bindEvent()
  }
  /**
   * @method
   * @desc 解除事件绑定
   */
  DrawingBoard.prototype.unBindEvent = function () {
    this.canvas.removeEventListener("mousedown", this.touchStartPC)
    this.canvas.removeEventListener("mousemove", this.touchMovePC)
    this.canvas.removeEventListener("mouseup", this.touchEndPC)
  }
示例
  <div id="drawingBoard"></div>
  <button id="disableBtn">禁用</button>
  <button id="enableBtn">启用</button>
  var board = new DrawingBoard({
    el: document.getElementById('drawingBoard'),
  })

  disableBtn.onclick = disabled
  enableBtn.onclick = enabled

  function disabled() {
    board.disable()
  }

  function enabled() {
    board.enable()
  }

设置画笔样式&画板背景

  /**
   * @method
   * @param {Event} event 事件对象
   * @desc 鼠标按下事件
   */
  DrawingBoard.prototype.touchStartPC = function (event) {
    _this.drawStatus = true
    const { offsetX, offsetY } = event

    // 新增
    if (_this.isRubber) {
      _this.eraseBoard(offsetX, offsetY)
      return
    }
    _this.createBrush(offsetX, offsetY)
  }

  /**
   * @method
   * @param {Event} event 事件对象
   * @desc 鼠标移动事件
   */
  DrawingBoard.prototype.touchMovePC = function (event) {
    if (!_this.drawStatus) return;
    const { offsetX, offsetY } = event

    // 新增
    if (_this.isRubber) {
      _this.eraseBoard(offsetX, offsetY)
      return
    }
    _this.drawPixel({offsetX, offsetY})
  }

  /**
   * 初始化画板
   */
  DrawingBoard.prototype.initSignatureCanvas = function () {
    let canvas = document.createElement('canvas')
    this.canvas = canvas
    this.addAttribute(canvas)
    this.bindEvent(canvas)
    this.ctx = canvas.getContext('2d')
    this.options.el?.appendChild(canvas)

    // 新增
    this.ctx.fillStyle = this.options.background || '#fff'
    this.ctx.fillRect(0, 0, canvas.width, canvas.height)
  }

  /**
   * @method
   * @param {Object} options 绘制线条的 x,y 坐标
   * @desc 根据点坐标绘制连线
   */
  DrawingBoard.prototype.drawPixel = function (options) {
    const {offsetX, offsetY} = options

    // 新增
    this.ctx.strokeStyle = this.options.brush.color || '#000'
    this.ctx.lineWidth = this.options.brush.lineWidth || 2
    // 设置绘制线段末端结束的形式
    this.ctx.lineCap = 'round'
    this.ctx.lineJoin = 'round'

    this.ctx.lineTo(offsetX, offsetY)
    this.ctx.fill()
    this.ctx.stroke();
    this.createBrush(offsetX, offsetY)
  }

  /**
   * @method
   * @param {Number} x 画笔的 x 坐标
   * @param {Number} y 画笔的 y 坐标
   * @desc 擦除画布
   */
  DrawingBoard.prototype.eraseBoard = function (x, y) {
    this.ctx.clearRect(x, y, this.options.brush.lineWidth, this.options.brush.lineWidth)
  }

  /**
   * @mehtod
   * @desc 切换画笔和橡皮模式
   */
  DrawingBoard.prototype.switchBrush = function () {
    if (this.isRubber) {
      this.isRubber = false
      this.options.el.style.cursor = `url(${this.options.brush.pointer}) 0 16, default`
    } else {
      this.isRubber = true
      this.options.el.style.cursor = `url('./assets/rubber.png') 0 16, default`
    }
  }

  /**
   * @mehtod
   * @param {Object} brush 画笔样式配置项
   * @desc 设置画笔样式
   */
  DrawingBoard.prototype.setBrush = function (brush) {
    Object.assign(this.options.brush, brush)
  }

options 配置项增加 background brush 属性,允许传入画布背景与画笔配置信息。brush 属性包含 画笔颜色:color,画笔粗细:lineWidth,画笔icon:pointer(目前仅支持设置PC端鼠标指针)。

示例
  <div id="drawingBoard"></div>
  <div>
    <label for="colorSelect">画笔颜色:</label>
    <input type="color" id="colorSelect">
  </div>
  <div>
    <label for="numberSelect">画笔粗细:</label>
    <input type="number" id="numberSelect">
  </div>
  var board = new DrawingBoard({
    el: document.getElementById('drawingBoard'),
    background: '#000',
    brush: {
      color: '#fff',
      lineWidth: 10,
      pointer: './assets/brush.png'
    }
  })

  colorSelect.onchange = colorSelectChange
  numberSelect.onchange = numberSelectChange

  function colorSelectChange (event) {
    board.setBrush({
      color: event.target.value
    })
  }

  function numberSelectChange(event) {
    board.setBrush({
      lineWidth: event.target.value
    })
  }

导出画布信息为图片资源

  /**
   * @method
   * @param {Function} cb 通过回调函数返回画布信息
   * @desc 保存画布信息为图片资源
   */
  DrawingBoard.prototype.save = function (cb) {
    if (this.options.exportType === 'blob') {
      this.canvas.toBlob(function (blob) {
        const blobUrl = URL.createObjectURL(blob)
        cb(blobUrl)
      }, this.options.mimeType)
    } else {
      const base64 = this.canvas.toDataURL(this.options.mimeType)
      cb(base64)
    }
  }

options 配置项增加 mimeType exportType 属性,mimeType 设置导出图片资源格式,默认 image/pngexportType 设置导出资源的数据形式,例如:base64 or blob,默认:base64,目前仅支持PC端,暂不支持移动端导出。

完整代码

文档

  /**
    * @method
    * @param {String} str 需要转换的字符串
    * @returns 转换后的字符串
    * @desc 将驼峰命名转化为使用分隔符的字符串
    */
  function toSplitLine(str) {
    var reg = /[A-Z]/g;
    var newStr = str.replace(reg, function ($0) {
      return '-' + $0.toLocaleLowerCase();
    });
    if (newStr.substring(0, 1) === '-') {
      newStr = newStr.substring(1);
    }
    return newStr;
  }

  /**
   * @class
   * @classdesc 实现手写签名的构造函数
   * @param {Object} 手写签名画板的配置项
   */
  function DrawingBoard(options) {
    if (!(this instanceof DrawingBoard)) {
      throw new TypeError("DrawingBoard constructor cannot be invoked without 'new'")
    }
    this.options = options
    this.canvas = null
    this.ctx = null
    this.drawStatus = false
    this.isDisable = true
    this.isRubber = false

    const _this = this

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标按下事件
     */
    DrawingBoard.prototype.touchStartPC = function (event) {
      _this.drawStatus = true
      const { offsetX, offsetY } = event
      if (_this.isRubber) {
        _this.eraseBoard(offsetX, offsetY)
        return
      }
      _this.createBrush(offsetX, offsetY)
    }

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标移动事件
     */
    DrawingBoard.prototype.touchMovePC = function (event) {
      if (!_this.drawStatus) return;
      const { offsetX, offsetY } = event
      if (_this.isRubber) {
        _this.eraseBoard(offsetX, offsetY)
        return
      }
      _this.drawPixel({offsetX, offsetY})
    }

    /**
     * @method
     * @param {Event} event 事件对象
     * @desc 鼠标抬起事件
     */
    DrawingBoard.prototype.touchEndPC = function (event) {
      _this.drawStatus = false
    }

    this.initSignatureCanvas()
  }

  /**
   * 初始化画板
   */
  DrawingBoard.prototype.initSignatureCanvas = function () {
    this.isDisable = false
    let canvas = document.createElement('canvas')
    this.canvas = canvas
    this.addAttribute(canvas)
    this.bindEvent(canvas)
    this.ctx = canvas.getContext('2d')
    this.options.el?.appendChild(canvas)
    this.ctx.fillStyle = this.options.background || '#fff'
    this.ctx.fillRect(0, 0, canvas.width, canvas.height)
  }

  /**
   * @method
   * @param {Number} x 画笔的 x 坐标
   * @param {Number} y 画笔的 y 坐标
   * @desc 移动画笔创建连接点
   */
  DrawingBoard.prototype.createBrush = function (x, y) {
    this.ctx.beginPath()
    this.ctx.moveTo(x, y)
  }

  /**
   * @method
   * @param {Object} options 绘制线条的 x,y 坐标
   * @desc 根据点坐标绘制连线
   */
  DrawingBoard.prototype.drawPixel = function (options) {
    const {offsetX, offsetY} = options

    this.ctx.strokeStyle = this.options.brush.color || '#000'
    this.ctx.lineWidth = this.options.brush.lineWidth || 2
    // 设置绘制线段末端结束的形式
    this.ctx.lineCap = 'round'
    this.ctx.lineJoin = 'round'
    this.ctx.lineTo(offsetX, offsetY)
    this.ctx.fill()
    this.ctx.stroke();
    this.createBrush(offsetX, offsetY)
  }


  /**
   * @method
   * @param {Number} x 画笔的 x 坐标
   * @param {Number} y 画笔的 y 坐标
   * @desc 擦除画布
   */
  DrawingBoard.prototype.eraseBoard = function (x, y) {
    this.ctx.clearRect(x, y, this.options.brush.lineWidth, this.options.brush.lineWidth)
  }


  /**
   * @mehtod
   * @desc 切换画笔和橡皮模式
   */
  DrawingBoard.prototype.switchBrush = function () {
    if (this.isRubber) {
      this.isRubber = false
      this.options.el.style.cursor = `url(${this.options.brush.pointer}) 0 16, default`
    } else {
      this.isRubber = true
      this.options.el.style.cursor = `url('./assets/rubber.png') 0 16, default`
    }
  }


  /**
   * @mehtod
   * @param {Object} brush 画笔样式配置项
   * @desc 设置画笔样式
   */
  DrawingBoard.prototype.setBrush = function (brush) {
    Object.assign(this.options.brush, brush)
  }

  /**
   * @method
   * @param {Function} cb 通过回调函数返回画布信息
   * @desc 保存画布信息为图片资源
   */
  DrawingBoard.prototype.save = function (cb) {
    if (this.options.exportType === 'blob') {
      this.canvas.toBlob(function (blob) {
        const blobUrl = URL.createObjectURL(blob)
        cb(blobUrl)
      }, this.options.mimeType)
    } else {
      const base64 = this.canvas.toDataURL(this.options.mimeType)
      cb(base64)
    }
  }

  /**
   * @method
   * @desc 禁用画布
   */
  DrawingBoard.prototype.disable = function () {
    this.isDisable = true
    this.unBindEvent()
  }

  /**
   * @method
   * @desc 启用画布
   */
  DrawingBoard.prototype.enable = function () {
    this.isDisable = false
    this.bindEvent()
  }

  /**
   * @method
   * @desc 获取父容器宽高
   */
  DrawingBoard.prototype.getClientRect = function () {
    return {
      width: this.options.el.scrollWidth,
      height: this.options.el.scrollHeight
    }
  }

  /**
   * @method
   * @desc 为容器和画布添加属性样式
   */
  DrawingBoard.prototype.addAttribute = function () {
    let { width, height, style } = this.options
    const container = this.getClientRect()
    let styleParse = ''

    for (const key in style) {
      if (Object.prototype.hasOwnProperty.call(style, key)) {
        styleParse += `${toSplitLine(key)}: ${style[key]};`
      }
    }

    if(!width) width = '200px'
    if(!height) height = '200px'

    this.canvas.setAttribute('width', width)
    this.canvas.setAttribute('height', height)
    this.canvas.setAttribute('style', styleParse)

    if (this.options.el) { 
      if(container.width <= 0) this.options.el.style.width = width
      if (container.height <= 0) this.options.el.style.height = height
      if (this.options.brush && this.options.brush.pointer) {
        this.options.el.style.cursor = `url(${this.options.brush.pointer}) 0 16, default`
      }
    }
  }

  /**
   * @method
   * @desc 事件绑定
   */
  DrawingBoard.prototype.bindEvent = function () {
    this.canvas.addEventListener("mousedown", this.touchStartPC)
    this.canvas.addEventListener("mousemove", this.touchMovePC)
    this.canvas.addEventListener("mouseup", this.touchEndPC)
  }

  /**
   * @method
   * @desc 解除事件绑定
   */
  DrawingBoard.prototype.unBindEvent = function () {
    this.canvas.removeEventListener("mousedown", this.touchStartPC)
    this.canvas.removeEventListener("mousemove", this.touchMovePC)
    this.canvas.removeEventListener("mouseup", this.touchEndPC)
  }

工具类函数

  /**
   * @method
   * @param {String} str 需要转换的字符串
   * @returns 转换后的字符串
   * @desc 将驼峰命名转化为使用分隔符的字符串
   */
  function toSplitLine(str) {
    var reg = /[A-Z]/g;
    var newStr = str.replace(reg, function ($0) {
      return '-' + $0.toLocaleLowerCase();
    });
    if (newStr.substring(0, 1) === '-') {
      newStr = newStr.substring(1);
    }
    return newStr;
  }

完结

以上就是整个功能的实现,如果存在缺陷请联系及时改正。
如果本文对你有帮助,记得留下点痕迹,让我知道你来过。
欢迎评论区讨论,共同进步,2022 即将结束,2023继续努力!!

完结

实现一个 Java Swing 手写电子签名系统,可以按照以下步骤进行: 1. 创建一个 JFrame 窗口,设置窗口大小和标题。 2. 在窗口中添加一个 JPanel,用于显示手写。 3. 在 JPanel 中实现鼠标监听器,监听鼠标按下、拖动和释放事件。 4. 在鼠标按下事件中,创建一个 BufferedImage 对象,用于保存手写上的图像。 5. 在鼠标拖动事件中,使用 Graphics2D 类的 drawLine() 方法,将鼠标移动轨迹绘制在 BufferedImage 上。 6. 在鼠标释放事件中,将 BufferedImage 保存为图片文件,完成签名。 以下是示例代码: ``` import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class SignaturePad extends JFrame implements MouseListener, MouseMotionListener { private JPanel panel; private BufferedImage image; private Graphics2D graphics; private int lastX, lastY; public SignaturePad() { super("电子签名系统"); panel = new JPanel(); panel.setPreferredSize(new Dimension(400, 300)); panel.addMouseListener(this); panel.addMouseMotionListener(this); getContentPane().add(panel); pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } @Override public void mousePressed(MouseEvent e) { lastX = e.getX(); lastY = e.getY(); image = new BufferedImage(panel.getWidth(), panel.getHeight(), BufferedImage.TYPE_INT_RGB); graphics = image.createGraphics(); graphics.setColor(Color.WHITE); graphics.fillRect(0, 0, panel.getWidth(), panel.getHeight()); } @Override public void mouseDragged(MouseEvent e) { int x = e.getX(); int y = e.getY(); graphics.setColor(Color.BLACK); graphics.drawLine(lastX, lastY, x, y); lastX = x; lastY = y; panel.getGraphics().drawImage(image, 0, 0, null); } @Override public void mouseReleased(MouseEvent e) { try { ImageIO.write(image, "png", new File("signature.png")); JOptionPane.showMessageDialog(this, "签名成功!"); } catch (IOException ex) { JOptionPane.showMessageDialog(this, "签名失败!"); } } public static void main(String[] args) { new SignaturePad(); } // 其他鼠标事件不需要实现 @Override public void mouseMoved(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} @Override public void mouseClicked(MouseEvent e) {} } ``` 运行以上代码,即可打开一个窗口,用鼠标在手写签名签名成功后会将签名保存为 PNG 图片文件。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值