方案一、使用vue-esign插件

安装依赖

npm install vue-esign --save

在main.js中引入:

import vueEsign from 'vue-esign'
Vue.use(vueEsign)

在组件中使用:

<template>
    <div>
        <vue-esign ref="esign" :width="500" :height="300" :isCrop="isCrop" :lineWidth="lineWidth" :lineColor="lineColor"
            :bgColor.sync="bgColor" />
        <button @click="handleReset">清空画板</button>
        <button @click="handleGenerate">生成图片</button>
        <div>
            <img :src="resultImg">
        </div>
    </div>
</template>

<script>
export default {
    data() {
        return {
            lineWidth: 6,
            lineColor: '#000000',
            bgColor: '',
            isCrop: false,
            resultImg: ''
        }
    },
    methods: {
        handleReset() {
            this.$refs.esign.reset()
        },
        handleGenerate() {
            this.$refs.esign.generate().then(res => {
                this.resultImg = res
                // 将base64转换为文件
                const blob = this.dataURLtoBlob(res)
                const file = this.blobToFile(blob, 'signature.jpg')
                // 可以上传到服务器
            }).catch(() => {
                alert('请先签名')
            })
        },
        dataURLtoBlob(dataurl) {
            const arr = dataurl.split(',')
            const mime = arr[0].match(/:(.*?);/)[1]
            const bstr = atob(arr[1])
            let n = bstr.length
            const u8arr = new Uint8Array(n)
            while (n--) {
                u8arr[n] = bstr.charCodeAt(n)
            }
            return new Blob([u8arr], { type: mime })
        },
        blobToFile(theBlob, fileName) {
            theBlob.lastModifiedDate = new Date()
            theBlob.name = fileName
            return theBlob
        }
    }
}
</script>

方案二、使用vue-signature-pad插件

下载依赖

npm install vue-signature-pad@2.0.5 --save

在main.js中引入

import Vue from 'vue'
import VueSignaturePad from 'vue-signature-pad'

Vue.use(VueSignaturePad)

在组件中使用:

<template>
  <div>
    <vue-signature-pad
      id="signature"
      width="95%"
      height="400px"
      ref="signaturePad"
      :options="options"
    />
    <button @click="save">保存</button>
    <button @click="clear">清除</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: {
        penColor: '#000',
      },
    }
  },
  methods: {
    save() {
      const { isEmpty, data } = this.$refs.signaturePad.saveSignature()
      if (isEmpty) {
        alert('请先签名');
        return;
      }
      console.log(data); // 这里是base64格式的签名图片
      // 可以发送到后端或保存到本地
    },
    clear() {
      this.$refs.signaturePad.clearSignature()
    },
  }
}
</script>

方案三、使用插件 signature_pad,支持撤销、重做等高级功能

安装依赖

npm install signature_pad

在Vue组件中使用:

<template>
  <div class="sign-box">
    <canvas ref="signCanvas"></canvas>
    <button @click="clear">清除</button>
    <button @click="save">保存</button>
  </div>
</template>

<script>
import SignaturePad from 'signature_pad';

export default {
  data() {
    return {
      signaturePad: null
    };
  },
  mounted() {
    const canvas = this.$refs.signCanvas;
    this.signaturePad = new SignaturePad(canvas, {
      penColor: 'rgb(0, 0, 0)'
    });
    
    // 校正签名位置偏移
    this.adjustSignatureImgPos();
  },
  methods: {
    adjustSignatureImgPos() {
      const canvas = this.$refs.signCanvas;
      const ratio = Math.max(window.devicePixelRatio || 1, 1);
      canvas.width = canvas.offsetWidth * ratio;
      canvas.height = canvas.offsetHeight * ratio;
      // canvas.getContext('2d').scale(ratio, ratio);
    },
    clear() {
      this.signaturePad.clear();
    },
    save() {
      const signatureImgSrc = this.signaturePad.toDataURL('image/png');
      console.log(signatureImgSrc); // base64格式的签名图片
      // 可以发送到后端或保存到本地
    }
  }
};
</script>

方案四、使用原生Canvas实现,但需要自行处理绘制逻辑和跨设备适配

使用原生实现

<template>
  <div class="signature-pad">
    <canvas 
      ref="signatureCanvas"
      @touchstart="startDrawingTouch"
      @touchmove="drawTouch"
      @touchend="stopDrawingTouch"
      @mousedown="startDrawing"
      @mousemove="draw"
      @mouseup="stopDrawing"
    ></canvas>
    <div class="controls">
      <button @click="clearCanvas">清除</button>
      <button @click="saveSignature">保存</button>
    </div>
    <img v-if="signatureImage" :src="signatureImage" alt="签名预览">
  </div>
</template>

<script>
export default {
  data() {
    return {
      isDrawing: false,
      lastX: 0,
      lastY: 0,
      signatureImage: null
    };
  },
  mounted() {
    this.initCanvas();
  },
  methods: {
    initCanvas() {
      const canvas = this.$refs.signatureCanvas;
      const ctx = canvas.getContext('2d');
      // 设置画布大小
      canvas.width = canvas.offsetWidth;
      canvas.height = canvas.offsetHeight;
      // 设置绘制样式
      ctx.strokeStyle = '#000000';
      ctx.lineWidth = 2;
      ctx.lineCap = 'round';
      ctx.lineJoin = 'round';
    },
    // PC端绘制
    startDrawing(e) {
      this.isDrawing = true;
      const canvas = this.$refs.signatureCanvas;
      [this.lastX, this.lastY] = [e.offsetX, e.offsetY];
    },
    draw(e) {
      if (!this.isDrawing) return;
      const canvas = this.$refs.signatureCanvas;
      const ctx = canvas.getContext('2d');
      ctx.beginPath();
      ctx.moveTo(this.lastX, this.lastY);
      ctx.lineTo(e.offsetX, e.offsetY);
      ctx.stroke();
      [this.lastX, this.lastY] = [e.offsetX, e.offsetY];
    },
    stopDrawing() {
      this.isDrawing = false;
    },
    // 移动端绘制
    startDrawingTouch(e) {
      this.isDrawing = true;
      const canvas = this.$refs.signatureCanvas;
      const touch = e.touches[0];
      const rect = canvas.getBoundingClientRect();
      [this.lastX, this.lastY] = [
        touch.clientX - rect.left,
        touch.clientY - rect.top
      ];
    },
    drawTouch(e) {
      if (!this.isDrawing) return;
      const canvas = this.$refs.signatureCanvas;
      const ctx = canvas.getContext('2d');
      const touch = e.touches[0];
      const rect = canvas.getBoundingClientRect();
      const x = touch.clientX - rect.left;
      const y = touch.clientY - rect.top;
      
      ctx.beginPath();
      ctx.moveTo(this.lastX, this.lastY);
      ctx.lineTo(x, y);
      ctx.stroke();
      [this.lastX, this.lastY] = [x, y];
      e.preventDefault(); // 防止页面滚动
    },
    stopDrawingTouch() {
      this.isDrawing = false;
    },
    // 清除画布
    clearCanvas() {
      const canvas = this.$refs.signatureCanvas;
      const ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      this.signatureImage = null;
    },
    // 保存签名
    saveSignature() {
      const canvas = this.$refs.signatureCanvas;
      this.signatureImage = canvas.toDataURL('image/png'); // 获取签名图像的Base64字符串
      // 可以在这里将base64发送到后端或保存到本地
    }
  }
};
</script>

<style scoped>
.signature-pad {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.signature-canvas {
  border: 1px solid #ccc;
  cursor: crosshair;
  width: 100%;
  height: 300px;
}
.controls {
  margin-top: 10px;
}
button {
  margin: 0 5px;
  padding: 8px 16px;
  background: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
}
img {
  margin-top: 10px;
  max-width: 100%;
  max-height: 300px;
}
</style>

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐