@touchstart=“touchStart”:这是一个触摸事件,当用户在移动设备上触摸屏幕并开始触摸 canvas 元素时触发
@touchmove=“touchMove”:这是一个触摸后滑动事件,当用户在移动设备上触摸屏幕并在 canvas 元素上滑动时触发
@touchend=“touchEnd”:这是一个触摸=结束事件,当用户在移动设备上触摸屏幕并结束触摸 canvas 元素时触发
@mousedown=“mouseDown”:这是一个鼠标点击事件,当用户在桌面设备上点击鼠标按钮时触发
@mousemove=“mouseMove”:这是一个鼠标滑动事件,当用户在桌面设备上移动鼠标时触发
@mouseup=“mouseUp”:这是一个鼠标离开事件,当用户在桌面设备上释放鼠标按钮时触发
第一种 这个移动端PC端都可以
<template>
<div class="homes">
<div class="head_div imgClass">
<img style="width: 100%;height: 100%;" :src="resultImg" alt="">
</div>
<div ref="canvasHW" class="body_div">
<canvas ref="canvasF" style="width: 100%;height: 100%;" @touchstart="touchStart" @touchmove="touchMove"
@touchend="touchEnd" @mousedown="mouseDown" @mousemove="mouseMove" @mouseup="mouseUp">
</canvas>
</div>
<div class="font_div">
<div style="margin-right: 20px;">
背景颜色:<input type="color" v-model="canvasBGC" @change="BGC_color" />
</div>
<div style="margin-right: 20px;">
文字颜色:<input type="color" v-model="strokeStyle" @change="Text_color" />
</div>
<el-button type="danger" @click.native.prevent="handleOverwrite()" size="mini">重签</el-button>
<el-button type="danger" @click.native.prevent="handleSave()" size="mini">确定</el-button>
</div>
</div>
</template>
<style scoped>
.homes {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.homes .head_div {
width: 100%;
height: 80px;
background-color: aquamarine;
}
.homes .font_div {
width: 100%;
height: 50px;
background-color: aquamarine;
display: flex;
}
.homes .imgClass {
width: 100%;
height: 200px;
}
.homes .body_div {
flex: 1;
box-sizing: border-box;
padding: 10px;
background-color: burlywood;
}
.body_div canvas {
border: 1px solid #00a8ff;
}
</style>
<script>
export default {
data() {
return {
resultImg: null,
points: [],
canvasTxt: null,
stage_info: [],
startX: 0,
startY: 0,
moveY: 0,
moveX: 0,
isDown: false,
strokeStyle: '#CA6363',
canvasBGC: '#8688D0',
lineWidth: 5,
config: {
width: 0, // 宽度
height: 0, // 高度
can_width: 0,
can_height: 0,
}
}
},
created() {
},
mounted() {
// this.canvasBGC = '#36d'
// this.strokeStyle = '#000'
this.initCanvas()
},
methods: {
// 初始化Canvas
initCanvas() {
const {
width, height,
} = document.querySelector('.body_div');
this.config.width = width;
this.config.height = height;
let canvas = this.$refs.canvasF
// 获取画布的宽度
canvas.width = this.$refs.canvasHW.offsetWidth - 20
this.config.can_width = canvas.width;
// 获取画布的高度
canvas.height = this.$refs.canvasHW.offsetHeight
this.config.height = canvas.height;
// 创建 context 对象
this.canvasTxt = canvas.getContext('2d')
this.stage_info = canvas.getBoundingClientRect()
// 设置canvas背景色
this.canvasTxt.fillStyle = this.canvasBGC;
this.canvasTxt.fillRect(0, 0, canvas.width, canvas.height);
},
BGC_color(event) {
console.log('选择的颜色', event.target.value);
this.canvasBGC = event.target.value
this.initCanvas()
},
Text_color(event) {
this.strokeStyle = event.target.value
},
// 鼠标按下事件 - 准备绘画
mouseDown(ev) {
ev = ev || event
ev.preventDefault()
if (ev) {
let obj = {
x: ev.offsetX,
y: ev.offsetY
}
this.startX = obj.x
this.startY = obj.y
this.canvasTxt.beginPath()
this.canvasTxt.moveTo(this.startX, this.startY)
this.canvasTxt.lineTo(obj.x, obj.y)
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.points.push(obj)
this.isDown = true
}
},
// 触摸开始事件 - 准备绘画
touchStart(ev) {
ev = ev || event
ev.preventDefault()
if (ev.touches.length == 1) {
let obj = {
x: ev.targetTouches[0].clientX - this.stage_info.left,
y: ev.targetTouches[0].clientY - this.stage_info.top
}
this.startX = obj.x
this.startY = obj.y
this.canvasTxt.beginPath()
this.canvasTxt.moveTo(this.startX, this.startY)
this.canvasTxt.lineTo(obj.x, obj.y)
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.points.push(obj)
}
},
// 鼠标移动事件 - 开始绘画
mouseMove(ev) {
ev = ev || event
ev.preventDefault()
if (this.isDown) {
let obj = {
x: ev.offsetX,
y: ev.offsetY
}
this.moveY = obj.y
this.moveX = obj.x
this.canvasTxt.strokeStyle = this.strokeStyle
this.canvasTxt.lineWidth = this.lineWidth
this.canvasTxt.beginPath()
this.canvasTxt.moveTo(this.startX, this.startY)
this.canvasTxt.lineTo(obj.x, obj.y)
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.startY = obj.y
this.startX = obj.x
this.points.push(obj)
}
},
// 触摸移动事件 - 开始绘画
touchMove(ev) {
ev = ev || event
ev.preventDefault()
if (ev.touches.length == 1) {
let obj = {
x: ev.targetTouches[0].clientX - this.stage_info.left,
y: ev.targetTouches[0].clientY - this.stage_info.top
}
this.moveY = obj.y
this.moveX = obj.x
// 设置线条颜色
this.canvasTxt.strokeStyle = this.strokeStyle
// 设置线条宽度
this.canvasTxt.lineWidth = this.lineWidth
// 绘制开始路径
this.canvasTxt.beginPath()
// 定义线条开始坐标
this.canvasTxt.moveTo(this.startX, this.startY)
// 定义线条结束坐标
this.canvasTxt.lineTo(obj.x, obj.y)
// 绘制线条
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.startY = obj.y
this.startX = obj.x
this.points.push(obj)
}
},
// 松开鼠标事件 - 停止绘画
mouseUp(ev) {
ev = ev || event
ev.preventDefault()
if (ev) {
let obj = {
x: ev.offsetX,
y: ev.offsetY
}
this.canvasTxt.beginPath()
this.canvasTxt.moveTo(this.startX, this.startY)
this.canvasTxt.lineTo(obj.x, obj.y)
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.points.push(obj)
this.points.push({ x: -1, y: -1 })
this.isDown = false
}
},
// 触摸结束事件 - 停止绘画
touchEnd(ev) {
ev = ev || event
ev.preventDefault()
if (ev.touches.length == 1) {
let obj = {
x: ev.targetTouches[0].clientX - this.stage_info.left,
y: ev.targetTouches[0].clientY - this.stage_info.top
}
this.canvasTxt.beginPath()
this.canvasTxt.moveTo(this.startX, this.startY)
this.canvasTxt.lineTo(obj.x, obj.y)
this.canvasTxt.stroke()
this.canvasTxt.closePath()
this.points.push(obj)
this.points.push({ x: -1, y: -1 })
}
},
// 重写
handleOverwrite() {
this.canvasTxt.clearRect(0, 0, this.$refs.canvasF.width, this.$refs.canvasF.height)
this.points = []
this.resultImg = null
},
handleSave() {
return new Promise((resolve, reject) => {
if (!this.isCanvasBlank(this.$refs.canvasF)) {
// this.rotateBase64(this.$refs.canvasF.toDataURL('image/png')).then((img) => {
// const imgBase64 = img;
// console.log(imgBase64, 'imgBase64-->>'); // base64编码
// this.resultImg = imgBase64
// });
this.resultImg = this.$refs.canvasF.toDataURL('image/png')
console.log(this.resultImg, 'imgBase64-->>'); // base64编码
} else {
const err = '请签名';
reject(err);
}
});
},
// 判断canvas对象是否空
isCanvasBlank(canvas) {
const blank = document.createElement('canvas'); // 系统获取一个空canvas对象
blank.width = this.config.width;
blank.height = this.config.height;
return canvas.toDataURL() === blank.toDataURL(); // 比较值相等则为空
},
// 将base64图片旋转90度以后上传
rotateBase64(imgBase64) {
return new Promise((resolve) => {
const imgView = new Image();
imgView.src = imgBase64;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const cutCoor = {
sx: 0,
sy: 0,
ex: 0,
ey: 0,
};
// 裁剪坐标
imgView.onload = () => {
const imgW = imgView.width;
const imgH = imgView.height;
const size = imgH;
canvas.width = size * 2;
canvas.height = size * 2;
cutCoor.sx = size;
cutCoor.sy = size - imgW;
cutCoor.ex = size + imgH;
cutCoor.ey = size + imgW;
context.translate(size, size);
context.rotate((Math.PI / 2) * 3);
context.drawImage(imgView, 0, 0);
const imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
canvas.width = imgH;
canvas.height = imgW;
context.putImageData(imgData, 0, 0);
resolve(canvas.toDataURL('image/png'));
};
});
},
}
}
</script>
第二种 这个只能移动端上
<template>
<div class="homes">
<div class="head_div imgClass">
<img style="width: 100%;height: 100%;" :src="resultImg" alt="">
</div>
<div class="body_div">
<canvas class="handWriting" :disable-scroll="true" @touchstart="touchStart" @touchmove="touchMove"
@touchend="touchEnd" id="canvas" ref="canvas"></canvas>
</div>
<div class="head_div">
<el-button type="danger" @click.native.prevent="handleOverwrite()" size="mini">重签</el-button>
<el-button type="danger" @click.native.prevent="handleSave()" size="mini">确定</el-button>
</div>
</div>
</template>
<script>
const $ = (name) => document.querySelector(name);
// 配置内容
const config = {
width: 0, // 宽度
height: 0, // 高度
lineWidth: 5, // 线宽
strokeStyle: '#000000', // 线条颜色
lineCap: 'round', // 设置线条两端圆角
lineJoin: 'round', // 线条交汇处圆角
};
// 偏移量
const client = {
offsetX: 0,
offsetY: 0,
};
let canvas;
let ctx;
export default {
data() {
return {
resultImg: null
}
},
created() {
},
mounted() {
this.init();
},
methods: {
// 初始化画板
init() {
const {
width, height, left, top,
} = $('.body_div').getBoundingClientRect();
config.width = width;
config.height = height;
client.offsetX = left;
client.offsetY = top;
// canvas 实例
canvas = $('#canvas');
// 设置宽高
canvas.width = config.width;
canvas.height = config.height;
// 设置边框
// canvas.style.border = '1px solid #000';
// 创建上下文
ctx = canvas.getContext('2d');
// 设置填充背景色
ctx.fillStyle = 'transparent';
// 绘制填充矩形
ctx.fillRect(
0, // x 轴起始绘制位置
0, // y 轴起始绘制位置
config.width, // 宽度
config.height, // 高度
);
},
// 触摸开始
touchStart(event) {
if (event.type != 'touchstart') return false;
event.preventDefault();
// 获取偏移量及坐标
const { clientX, clientY } = event.changedTouches[0];
// 清除以上一次 beginPath 之后的所有路径,进行绘制
ctx.beginPath();
// 根据配置文件设置相应配置
ctx.lineWidth = config.lineWidth;
ctx.strokeStyle = config.strokeStyle;
ctx.lineCap = config.lineCap;
ctx.lineJoin = config.lineJoin;
// 设置画线起始点位(减去 左边、上方的偏移量很关键)
ctx.moveTo(clientX - client.offsetX, clientY - client.offsetY);
// console.log('触摸开始', e);
},
// 滑动开始
touchMove(ev) {
if (ev.type != 'touchmove') return false;
ev.preventDefault();
// 获取当前坐标点位
const { clientX, clientY } = ev.changedTouches[0];
// 根据坐标点位移动添加线条(减去 左边、上方的偏移量很关键)
ctx.lineTo(clientX - client.offsetX, clientY - client.offsetY);
// 绘制
ctx.stroke();
},
// 离开屏幕开始
touchEnd(ev) {
if (ev.type != 'touchend') return 0;
ev.preventDefault()
ctx.closePath();
// 移除鼠标移动或手势移动监听器
window.removeEventListener('mousemove', this.draw);
},
// 重写
handleOverwrite() {
ctx.clearRect(0, 0, config.width, config.height);
this.resultImg = ''
},
// 确定
handleSave() {
return new Promise((resolve, reject) => {
if (!this.isCanvasBlank(canvas)) {
// this.rotateBase64(canvas.toDataURL('image/png')).then((img) => {
// const imgBase64 = img;
// console.log(imgBase64, 'imgBase64-->>'); // base64编码
// resolve(imgBase64);
// });
this.resultImg = canvas.toDataURL('image/png')
} else {
const err = '请签名';
reject(err);
}
});
},
// 判断canvas对象是否空
isCanvasBlank(canvas) {
const blank = document.createElement('canvas'); // 系统获取一个空canvas对象
blank.width = config.width;
blank.height = config.height;
return canvas.toDataURL() === blank.toDataURL(); // 比较值相等则为空
},
// 将base64图片旋转90度以后上传
rotateBase64(imgBase64) {
return new Promise((resolve) => {
const imgView = new Image();
imgView.src = imgBase64;
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const cutCoor = {
sx: 0,
sy: 0,
ex: 0,
ey: 0,
};
// 裁剪坐标
imgView.onload = () => {
const imgW = imgView.width;
const imgH = imgView.height;
const size = imgH;
// 常量大小 = imgH;
canvas.width = size * 2;
canvas.height = size * 2;
cutCoor.sx = size;
cutCoor.sy = size - imgW;
cutCoor.ex = size + imgH;
cutCoor.ey = size + imgW;
context.translate(size, size);
context.rotate((Math.PI / 2) * 3);
context.drawImage(imgView, 0, 0);
const imgData = context.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey);
canvas.width = imgH;
canvas.height = imgW;
context.putImageData(imgData, 0, 0);
resolve(canvas.toDataURL('image/png'));
};
});
},
}
}
</script>
<style scoped>
.homes {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
}
.homes .head_div {
width: 100%;
height: 50px;
background-color: aquamarine;
}
.homes .imgClass {
width: 100%;
height: 200px;
}
.homes .body_div {
flex: 1;
background-color: burlywood;
}
</style>