在开源项目中发现canvas实现签名功能以此记录:http://www.youlai.tech/pages/52d5c3/
HTML:
<div class="canvas-dom"> <el-button plain type="text" style="margin-left:20px;margin-top:20px;font-size:18px;" @click="back">返回</el-button> <canvas ref="canvas" height="200" width="500" @mousedown="onEventStart" @mousemove.stop.prevent="onEventMove" @mouseup="onEventEnd" @touchstart="onEventStart" @touchmove.stop.prevent="onEventMove" @touchend="onEventEnd"> </canvas> <header> <el-button type="primary" @click="handleSaveImg">保存为图片</el-button> <el-button @click="handleToFile"> 保存到后端 </el-button> <el-button @click="handleClearSign"> 清空签名 </el-button> </header> <img v-if="imgUrl" :src="imgUrl" alt="签名" /> </div>
script:
<script setup lang="ts"> import { ref, onMounted } from 'vue' import { ElMessage } from 'element-plus' // import { uploadFileApi } from "@/api/file" const imgUrl = ref("") const canvas = ref() let ctx: CanvasRenderingContext2D // 正在绘制中,用来控制 move 和 end 事件 let painting = false // 获取触发点相对被触发dom的左、上角距离 const getOffset = (event: MouseEvent | TouchEvent) => { let offset: [number, number] if ((event as MouseEvent).offsetX) { // pc端 const { offsetX, offsetY } = event as MouseEvent; offset = [offsetX, offsetY] } else { // 移动端 const { top, left } = canvas.value.getBoundingClientRect() const offsetX = (event as TouchEvent).touches[0].clientX - left const offsetY = (event as TouchEvent).touches[0].clientY - top offset = [offsetX, offsetY] } return offset; }; // 绘制起点 let startX = 0, startY = 0 // 鼠标/触摸 按下时,保存 触发点相对被触发dom的左、上 距离 const onEventStart = (event: MouseEvent | TouchEvent) => { [startX, startY] = getOffset(event) painting = true } const onEventMove = (event: MouseEvent | TouchEvent) => { if (painting) { // 鼠标/触摸 移动时,保存 移动点相对 被触发dom的左、上 距离 const [endX, endY] = getOffset(event) paint(startX, startY, endX, endY, ctx) // 每次绘制 或 清除结束后,起点要重置为上次的终点 startX = endX startY = endY } }; const onEventEnd = () => { if (painting) { painting = false; // 停止绘制 } }; onMounted(() => { ctx = canvas.value.getContext("2d") as CanvasRenderingContext2D }); const handleToFile = async () => { if (isCanvasBlank(canvas.value)) { ElMessage({ type: "warning", message: "当前签名文件为空", }) return } const file = dataURLtoFile(canvas.value.toDataURL(), "签名.png") if (!file) return // 文件上传后端,返回地址,显示 // const { data } = await uploadFileApi(file) // handleClearSign(); // imgUrl.value = data.url }; const handleClearSign = () => { ctx.clearRect(0, 0, canvas.value.width, canvas.value.height); }; const isCanvasBlank = (canvas: HTMLCanvasElement) => { const blank = document.createElement("canvas"); //系统获取一个空canvas对象 blank.width = canvas.width; blank.height = canvas.height; return canvas.toDataURL() == blank.toDataURL(); //比较值相等则为空 }; // 保存为图片 const handleSaveImg = () => { if (isCanvasBlank(canvas.value)) { ElMessage({ type: "warning", message: "当前签名文件为空", }) return } const el = document.createElement("a") // 设置 href 为图片经过 base64 编码后的字符串,默认为 png 格式 el.href = canvas.value.toDataURL() el.download = "签名"; // 创建一个点击事件并对 a 标签进行触发 const event = new MouseEvent("click") el.dispatchEvent(event); } // 转为file格式,可传递给后端 const dataURLtoFile = (dataurl: string, filename: string) => { const arr: string[] = dataurl.split(",") if (!arr.length) return const mime = arr[0].match(/:(.*?);/) if (mime) { const bstr = atob(arr[1]) let n = bstr.length const u8arr = new Uint8Array(n) while (n--) { u8arr[n] = bstr.charCodeAt(n) } return new File([u8arr], filename, { type: mime[1] }) } }; // canvas 画图 function paint( startX: number, startY: number, endX: number, endY: number, ctx: CanvasRenderingContext2D ) { ctx.beginPath() ctx.globalAlpha = 1 ctx.lineWidth = 2 ctx.strokeStyle = "#000" ctx.moveTo(startX, startY) ctx.lineTo(endX, endY) ctx.closePath() ctx.stroke() } // 橡皮 function eraser( startX: number, startY: number, endX: number, endY: number, ctx: CanvasRenderingContext2D, size: number, shape: "rect" | "circle" ) { ctx.beginPath() ctx.globalAlpha = 1 switch (shape) { case "rect": ctx.lineWidth = size ctx.strokeStyle = "#fff" ctx.moveTo(startX, startY) ctx.lineTo(endX, endY) ctx.closePath() ctx.stroke() break case "circle": ctx.fillStyle = "#fff"; ctx.arc(startX, startY, size, 0, 2 * Math.PI) ctx.fill() break } } const back = () => { window.history.back() } </script>
css:
<style scoped lang="scss"> .canvas-dom { width: 100%; height: 100%; padding: 0 20px; background-color: #fff; display: flex; flex-direction: column; justify-content: center; align-items: center; canvas { border: 1px solid #e6e6e6; } header { display: flex; flex-flow: row nowrap; align-items: center; margin: 8px; .eraser-option { display: flex; label { white-space: nowrap; } } } } </style>
代码地址:https://gitee.com/yuexiayunsheng/vue3learn/blob/master/src/views/Signature.vue
标签:canvas,const,实现,ctx,number,签名,startX,event From: https://www.cnblogs.com/foxing/p/17648337.html