<template>
<section>
<div class="sign-wrap">
<div class="main">
<div class="box" style="width: 100%;height: 100%">
<!-- <vue-esign ref="esign" :width="600" :height='1375' :isCrop="isCrop" :lineWidth="lineWidth" :lineColor="lineColor" :bgColor.sync="bgColor" /> -->
<div class="drawing-board">
<canvas id="canvas" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd"></canvas>
</div>
</div>
</div>
<div class="dialog-header">签名确认</div>
<div class="dialog-footer btns">
<div class="btn confirm-btn confirm-btn1" @click="reset">重置</div>
<div class="btn confirm-btn " @click="save">确认</div>
</div>
</div>
</section>
</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;
// import { XButton } from "vux";
export default {
// components: { XButton },
data() {
return {
lineWidth: 6,
lineColor: "#000000",
bgColor: "#ccc",
resultImg: "", //base64结果数据
isCrop: true, //是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
};
},
mounted() {
setTimeout(() => {
if($('.drawing-board')){
this.drawingBoardInit()
}
}, 600)
},
methods: {
drawingBoardInit() {
const {
width,
height,
left,
top,
} = $('.drawing-board').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) {
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);
},
// 绘制
touchMove(event) {
// 获取当前坐标点位;
const {
clientX,
clientY
} = event.changedTouches[0];
// 根据坐标点位移动添加线条(减去 左边、上方的偏移量很关键)
ctx.lineTo(clientX - client.offsetX, clientY - client.offsetY);
// 绘制
ctx.stroke();
},
// 结束绘制
touchEnd() {
// 结束绘制
ctx.closePath();
// 移除鼠标移动或手势移动监听器
window.removeEventListener('mousemove', this.draw);
},
// 清除
reset() {
// 清空当前画布上的所有绘制内容
ctx.clearRect(0, 0, config.width, config.height);
},
// 将画布内容保存为图片
save() {
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编码
this.$emit("handleImg", imgBase64);
});
} else {
const err = '请签名';
reject(err);
}
});
// 将canvas上的内容转成blob流
// canvas.toBlob((blob) => {
// console.log(blob, 'blob-->>'); // 文件二进制流
// // 获取当前时间,用来当做文件名
// const date = new Date().getTime();
// // 创建一个 a 标签
// const link = document.createElement('a');
// // 设置 a 标签的下载文件名
// link.download = `${date}.png`;
// // 设置 a 标签的跳转路径为 文件流地址
// link.href = URL.createObjectURL(blob);
// // 手动触发 a 标签的点击事件
// link.click();
// // 移除 a 标签
// link.remove();
// });
},
// 判断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'));
};
});
},
// // 初始化方法
// init() {
// this.$nextTick(() => {
// this.$refs.esign.reset();
// });
// },
// // 清空画板
// handleReset() {
// this.$refs.esign.reset();
// },
// // 生成照片
// handleGenerate() {
// // 生成图片
// // 可选配置参数 ,在未设置format或quality属性时可在生成图片时配置 例如: {format:'image/jpeg', quality: 0.5}
// // this.$refs.esign.generate({format:'image/jpeg', quality: 0.5})
// this.$refs.esign.generate().then((base64) => {
// this.resultImg = base64; //默认生成的是base64形式的图片
// // 将生成的base64格式的图片传给父组件
// this.$emit("handleImg", base64);
// // 如果需要下载
// // const a = document.createElement("a");
// // a.href = res;
// // a.download = "签名.png";
// // a.click();
// // a.remove();
// })
// .catch((err) => {
// this.toast_warn(err); // 画布没有签字时会执行这里 'Not Signned'
// });
// },
},
};
</script>
<style lang="less">
section {
/* height: calc(100% - 44px) */
height:100%;
}
.sign-wrap {
height: 100%;
position: relative;
.main {
background-color: #ffffff;
padding: 20px 50px 20px 77px;
height: 100%;
}
.box {
margin: 0 auto;
background: #ecf0fa;
border-radius: 14px;
width: 100%;
height: 100%;
overflow: hidden;
}
.dialog-footer {
display: flex;
align-items: center;
justify-content: space-around;
width: 100%;
.btn {
font-size: 16px;
margin-left: 14px;
color: #666;
padding: 14px 34px;
text-align: center;
box-sizing: border-box;
background-color: #f8f8f8;
border-radius: 4px;
border-radius: 12px;
}
.confirm-btn {
background-color: #117af1;
color: #fff;
}
.confirm-btn1 {
background-color: #fff;
border: 1px solid #117af1;
color: #117af1;
}
}
}
.dialog-header {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-ms-flex-pack: distribute;
justify-content: space-around;
width: 100%;
position: absolute;
-webkit-transform: rotate(90deg);
top: 9%;
transform: rotate(90deg);
right: -3%;
font-size: 19px;
width: auto !important;
font-weight: bold;
}
.btns {
position: absolute;
-webkit-transform: rotate(90deg);
bottom: 18%;
transform: rotate(90deg);
left: -20%;
width: auto !important;
}
.drawing-board {
width: 100%;
height: calc(100%);
/* border-bottom: 1px solid #ccc; */
box-sizing: border-box;
}
.tool-bar {
width: 100%;
height: 40px;
display: flex;
justify-content: flex-end;
align-items: center;
position: absolute;
top: 46%;
left: -4rem;
transform: rotate(90deg);
.van-button {
flex: 1
}
}
</style>
vue项目 移动端 手写签名
于 2024-04-02 15:02:16 首次发布