1、首先我们来了解一下 canvas 的基本使用
在 canvas 标签中间写的内容会在浏览器不兼容的情况下显示
<canvas class="canvas" ref="canvas">
抱歉,您的浏览器不支持canvas元素
</canvas>
我们先来熟悉一下本次任务中 canvas 使用的API(其他 API 可参考 https://blog.youkuaiyun.com/Insist_bin/article/details/80282043)
const canvas = document.getElementQuerySelector('canvas');
// 获取 canvas 上下文
const cxt = canvas.getContext('2d');
// 设置 canvas 颜色
cxt.strokeStyle = '#000';
// 设置 canvas 线条宽度
cxt.lineWidth = 3;
// 改变线条末端线帽的样式,为每个末端添加圆形线帽,减少线条的生硬感
cxt.lineCap = 'round';
// 指定条线交汇时为圆形边角
cxt.lineJoin = 'round';
// 绘制元素阴影的功能来模糊边缘出现的锯齿
cxt.shadowBlur = 1;
cxt.shadowColor = '#000';
// 清除当前路径,开始新的路径
context.beginPath();
// 设置绘制起始坐标
cxt.moveTo(10, 10); // 就是从 x = 10, y = 10 的坐标开始绘制
// 设置直线到的位置
cxt.lineTo(10, 100); // 就是从 x = 10, y = 10 绘制到 x = 10, y = 100 的一条路径
// 绘制完路径之后需要描边
cxt.stroke();
// 注意点绘制的都是路径,需要通过 stroke 描边才能显示
使用到的事件
- mousedown:PC 端鼠标按下
- mousemove:PC 端鼠标移动
- mouseup:PC 端松起鼠标
- touchstart:当手指触摸屏幕时候触发
- touchmove:当手机在滑动屏幕的时候连续触发
- touchend:当手指从屏幕上离开的时候触发
下面我们开始在 Vue 项目中创建 signature 组件了
整体的思路是:鼠标点下去那一刻,记录点击那点的坐标,并开始监听移动事件,每次触发移动事件的时候,获取移动的坐标,然后把起始坐标到结束坐标绘制线,接着把移动的坐标再次作为起始坐标,由于移动事件会不断的触发,不断触发移动事件就能不断绘制一点一点,最终形成路径。当鼠标松开的时候记得取消监听移动事件,不然会一直绘制下去。
<template>
<div>
<div class="signature">
<canvas class="canvas" ref="canvas">
抱歉,您的浏览器不支持canvas元素
</canvas>
<div class="button-box">
<button @click="surewrite">确定</button>
<button @click="overwrite">重写</button>
</div>
</div>
</div>
</template>
export default {
name: "signature",
// 可自定义宽高
props: {
height: {
type: Number,
default: 150
},
width: {
type: Number,
default: 300
}
},
data() {
return {
canvas: null,
cxt: null,
// 判断当前设备是手机还是电脑
device: ('ontouchstart' in window) ? 'phone' : 'computer',
// 根据设备定义不同的事件组
events: ('ontouchstart' in window) ?
['touchstart', 'touchmove', 'touchend'] :
['mousedown', 'mousemove', 'mouseup'],
startX: 0,
startY: 0,
lineWidth: 3,
isDraw: false, // 表示canvas上是否已经签过名,哪怕是一个点
drawSuc: false // 点击签名成功
};
},
mounted() {
// 因为需要操作 DOM 所以初始化 canvas 需要放在 mounted 中
this.initCanvas()
},
methods: {
// 初始化 canvas
initCanvas() {
const canvas = this.$refs.canvas;
canvas.width = this.width;
canvas.height = this.height;
this.canvas = canvas;
const cxt = canvas.getContext('2d');
this.rect = canvas.getBoundingClientRect();
this.cxt = cxt;
cxt.strokeStyle = '#000';
cxt.lineCap = 'round';
cxt.lineWidth = this.lineWidth;
cxt.lineJoin = 'round';
cxt.shadowBlur = 1;
cxt.shadowColor = '#000';
// 为 canvas 监听第一个事件
canvas.addEventListener(this.events[0], this.startEventHandler, false)
},
// 传入起始点和终点绘制
drawLine(context, startX, startY, moveX, moveY) {
context.beginPath();
context.moveTo(startX, startY)
context.lineTo(moveX, moveY)
context.stroke();
context.closePath();
},
startEventHandler(e) {
// 如果已经点击确认签名了则不能修改了,除非点击重写
if (this.drawSuc)
return
let ev = e || event;
ev.preventDefault();
// offsetX/Y 是相对于带有定位的父盒子的x,y坐标
// 因为 offsetX 和 offsetY 在火狐不兼容
// 所以使用 clientX - rect.left
// clientX/Y 鼠标相对于浏览器窗口可视区域的X,Y坐标的距离
// rect.left/top 元素距离页面左边和上边的距离
this.cxt.beginPath()
// 记录起始点
if (this.device === 'computer') {
let rect = this.canvas.getBoundingClientRect()
this.startX = ev.clientX - rect.left;
this.startY = ev.clientY - rect.top;
} else {
const touch = e.changedTouches[0];
this.startX = touch.pageX;
this.startY = touch.pageY;
}
this.canvas.addEventListener(this.events[1], this.moveEventHandler, false);
this.isDraw = true;
},
moveEventHandler(e) {
let ev = e || event;
let moveX;
let moveY;
this.cxt.beginPath()
if (this.device === 'computer') {
let rect = this.canvas.getBoundingClientRect()
moveX = ev.clientX - rect.left;
moveY = ev.clientY - rect.top;
} else {
const touch = e.changedTouches[0];
moveX = touch.pageX;
moveY = touch.pageY;
}
this.drawLine(this.cxt, this.startX, this.startY, moveX, moveY);
this.startX = moveX;
this.startY = moveY;
this.canvas.addEventListener(this.events[2], this.endEventHandler, false);
},
endEventHandler() {
this.cxt.closePath();
this.canvas.removeEventListener(this.events[1], this.moveEventHandler)
},
//重写
overwrite() {
this.cxt.clearRect(0, 0, this.width, this.height);
this.isDraw = false;
this.drawSuc = false
},
//确认签名
surewrite() {
// 确认签名后把 canvas 转换成图片
const imgBase64 = this.canvas.toDataURL();
console.log("保存签名成功" + imgBase64);
this.drawSuc = true
if (this.isDraw) {
alert("签名成功!");
// 把图片通过回调方式返回给外部
this.$emit("surewrite", imgBase64);
} else {
alert("请签名后再确认!");
}
}
}
}
.canvas {
border: 1px solid #000;
}
.signature {
box-sizing: border-box;
overflow: hidden;
z-index: 100;
display: flex;
}
.button-box {
padding: 5px;
text-align: center;
display: flex;
flex-direction: column;
}
.btnBox {
display: flex;
flex-direction: column;
padding: 5px;
text-align: center;
line-height: 30px;
}
.button-box button {
border: 1px solid dodgerblue;
background: dodgerblue;
color: #fff;
border-radius: 4px;
padding: 1px 25px;
width: 80px;
margin: 10px 15px;
font-size: 14px;
}
这张图表示的是client,screen,offset 的区别
下面是效果图
<signature :width="600" :height="500"></signature>