1.首先创建文件夹drawCanvas,里面新建index.vue和index.js
结构如下,根据项目使用情况选择按需注册或者全局注册
index.js文件如下,这个基本上无需改动,先cv抄作业
/**入参:
@text: 传入水印需要展示的文本;
@ID: canvas画板的id,用于获取元素,因为我的项目里面可能有多个签名,所以用id区分
@FWidth: canvas画板宽度
@FHeight: canvas画板高度
*/
const setWatermark = (text, ID, FWidth, FHeight) => {
const id = '1.23452384164.123412415';
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id));
}
const can = document.createElement('canvas');
// 设置canvas画布大小
can.width = text.length * 14 < 100 ? 100 : text.length * 14;
can.height = text.length * 14 < 75 ? 75 : text.length * 14;
let XNumber = Math.ceil(FWidth / can.width);
let YNumber = Math.ceil(FHeight / can.height);
const cans = can.getContext('2d');
cans.rotate((-30 * Math.PI) / 180); // 水印旋转角度
cans.font = '14px';
cans.fillStyle = '#999999';
// cans.fillText(str1, 40, can.height) // 水印在画布的位置x,y轴
cans.font = 'normal 14px';
cans.fillText(text, 0, can.height / 2, can.height);
let board = document.getElementById(`container${ID}`);
can.style.pointerEvents = 'none';
can.style.top = '0px';
can.style.left = '0px';
can.style.opacity = '0.15';
can.style.position = 'absolute';
can.style.zIndex = '100000';
const imageData = cans.getImageData(0, 0, can.width, can.height);
for (let i = 0; i < imageData.data.length; i += 4) {
// 当该像素是透明的,则设置成白色
if (imageData.data[i + 3] === 0) {
imageData.data[i] = 246;
imageData.data[i + 1] = 245;
imageData.data[i + 2] = 246;
imageData.data[i + 3] = 246;
}
}
cans.putImageData(imageData, 0, 0);
let img = new Image();
img.src = can.toDataURL('image/png', 1);
img.onload = () => {
for (let i = 0; i < XNumber; i++) {
board.getContext('2d').drawImage(img, can.width * i, 0);
for (let j = 1; j < YNumber; j++) {
board.getContext('2d').drawImage(img, can.width * i, can.height * j);
}
}
};
// board.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
board.appendChild(can);
return id;
};
// 添加水印方法
export const setWaterMark = (text, ID, FWidth, FHeight) => {
let id = setWatermark(text, ID, FWidth, FHeight);
if (document.getElementById(id) !== null) {
id = setWatermark(text, ID, FWidth, FHeight);
}
};
2. index.vue文件 按需求自行更改优化样式
<template>
<div class="zzh_box_canvas" :class="'zzh_box_canvas' + canvasKey" :style="{ width: width, height: height, maxWidth: '90vw', maxHeight: '90vh' }" v-loading="loading">
<div class="zzh_top" :class="'zzh_top' + canvasKey">
<el-button v-show="showImg && !disabled" class="toSave" @click="handlerClick('edit')" icon="edit" size="small" :disabled="disabled">签名</el-button>
<el-button v-show="!showImg" class="toSave" @click="handlerClick('cancel')" size="small">取消</el-button>
<!-- 这里是el-button组件,也可以自己按需求修改 -->
</div>
<div v-show="!showImg" id="board">
<canvas
:id="'container' + canvasKey"
:width="canvasWidth + 'px'"
:height="canvasHeight + 'px'"
@mousedown="mousedown($event)"
@mousemove="mousemove($event)"
@mouseup="mouseup"
@mouseleave="mouseup"
@touchstart="mousedown($event, 'touch')"
@touchmove="mousemove($event, 'touch')"
@touchend="mouseup('touch')"
></canvas>
<div class="cleanUp" @click.stop="toClear()">清除签名</div>
<div class="cleanUp2" @click.stop="handlerClick('submit')">确认签名</div>
</div>
<img v-show="showImg && value" :style="{ maxWidth: canvasWidth + 'px', height: canvasHeight + 'px' }" :src="value" alt="" />
<div v-show="showImg && !value" :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', border: '2px dashed #cccccc', background: '#f6f5f6', textAlign: 'center', lineHeight: canvasHeight + 'px', color: '999999' }">暂无签名</div>
</div>
</template>
<script>
import { setWaterMark } from './index.js';
import upload from '@/utils/fileUpload.js'; //这里是项目的上传接口,自行更改
export default {
name: 'drawCanvas',
props: {
//默认值 src
value: {
default: '',
},
name: {
default: '',
},
width: {
default: '100%',
},
height: {
default: '100%',
},
disabled: {
default: false,
},
},
data() {
return {
canvasKey: '', //唯一key值
cxt: null,
eraser: null,
canvas: null,
paint: false,
canvasWidth: '',
canvasHeight: '',
showImg: true,
loading: false,
};
},
components: {},
created() {
this.canvasKey = (Math.random() + '').slice(2);
},
mounted() {
this.canvasWidth = document.querySelector('.zzh_box_canvas' + this.canvasKey).offsetWidth - 4;
this.canvasHeight = document.querySelector('.zzh_box_canvas' + this.canvasKey).offsetHeight - document.querySelector('.zzh_top' + this.canvasKey).offsetHeight - 8;
this.$nextTick(() => {
this.initPage();
});
},
methods: {
async handlerClick(type) {
if (type == 'edit') {
this.showImg = false;
} else if (type == 'cancel') {
this.showImg = true;
} else if (type == 'submit') {
this.loading = true;
let base64Img = this.canvas.toDataURL('image/jpg');
//转换图片文件
const imgFile = this.base64ImgtoFile(base64Img);
upload(
imgFile,
res => {
let temp = {
name: res.attachment,
url: res.host + '/' + res.aliyunAddress,
};
this.showImg = true;
this.loading = false;
//抛出修改后的url
console.log('修改后的url文件:', temp);
this.$emit('input', temp.url, temp);
this.$emit('change', temp.url, temp);
},
err => {
this.loading = false;
},
res => {
this.loading = false;
}
);
}
},
getPoint(obj) {
//获取某元素以浏览器左上角为原点的坐标
var t = obj.offsetTop; //获取该元素对应父容器的上边距
var l = obj.offsetLeft; //对应父容器的上边距
//判断是否有父容器,如果存在则累加其边距
while ((obj = obj.offsetParent)) {
//等效 obj = obj.offsetParent;while (obj != undefined)
t += obj.offsetTop; //叠加父容器的上边距
l += obj.offsetLeft; //叠加父容器的左边距
}
// alert("top: " + t + " left: " + l);
return { t, l };
},
moveFn(e) {
e.preventDefault();
e.stopPropagation();
},
mousedown(e, type) {
this.page_stop();
// 鼠标按下事件
if (type == 'touch') {
e.preventDefault(); //阻止鼠标事件
let { t, l } = this.getPoint(document.querySelector('#container' + this.canvasKey));
let x = e.touches[0].pageX - l - 2; //这里的2是border宽度
let y = e.touches[0].pageY - t - 2;
console.log(x, y);
this.startPoint = {
x: x,
y: y,
};
this.paint = true;
} else {
let x = e.offsetX;
let y = e.offsetY;
console.log(x, y);
this.startPoint = {
x: x,
y: y,
};
this.paint = true;
}
},
mousemove(e, type) {
// 鼠标移动事件
e.preventDefault();
if (type == 'touch') {
let { t, l } = this.getPoint(document.querySelector('#container' + this.canvasKey));
let x = e.touches[0].pageX - l - 2; //这里的2是border宽度
let y = e.touches[0].pageY - t - 2;
this.endPoint = {
x: x,
y: y,
};
if (this.paint) {
if (this.eraser) {
this.cxt.clearRect(x, y, 10, 10);
} else {
this.draw();
}
this.startPoint.x = this.endPoint.x;
this.startPoint.y = this.endPoint.y;
}
} else {
const x = e.offsetX;
const y = e.offsetY;
this.endPoint = {
x: x,
y: y,
};
if (this.paint) {
if (this.eraser) {
this.cxt.clearRect(x, y, 10, 10);
} else {
this.draw();
}
this.startPoint.x = this.endPoint.x;
this.startPoint.y = this.endPoint.y;
}
}
},
draw() {
// 开始绘制
this.cxt.beginPath();
// 设置线条样式
this.cxt.lineWidth = '5';
this.cxt.lineCap = 'round';
this.cxt.lineJoin = 'round';
//起始位置
this.cxt.moveTo(this.startPoint.x, this.startPoint.y);
//停止位置
this.cxt.lineTo(this.endPoint.x, this.endPoint.y);
//描绘线路
this.cxt.stroke();
//结束绘制
this.cxt.closePath();
// clearTimeout(this.timer);
// this.timer = null;
// this.timer = setTimeout(_ => {
// this.handlerClick('submit');
// clearTimeout(this.timer);
// this.timer = null;
// }, 300);
},
mouseup() {
// 鼠标松开事件
this.paint = false;
this.page_move();
},
initPage() {
setWaterMark(this.name, this.canvasKey, this.canvasWidth, this.canvasHeight);
this.canvas = document.getElementById(`container${this.canvasKey}`);
this.cxt = this.canvas.getContext('2d');
},
toClear() {
this.cxt.fillStyle = 'transparent';
this.cxt.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
this.cxt.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.$nextTick(() => {
this.initPage();
this.$emit('input', '', {}); //清空签名
this.$emit('change', '', {});
});
},
base64ImgtoFile(dataurl, filename = 'file') {
let arr = dataurl.split(',');
let mime = arr[0].match(/:(.*?);/)[1];
let suffix = mime.split('/')[1];
let bstr = atob(arr[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], `${filename}.${suffix}`, {
type: mime,
});
},
/***滑动限制***/
page_stop() {
var mo = function(e) {
e.preventDefault();
};
document.body.style.overflow = 'hidden';
document.addEventListener('touchmove', mo, false); //禁止页面滑动
},
/***取消滑动限制***/
page_move() {
var mo = function(e) {
e.preventDefault();
};
document.body.style.overflow = ''; //出现滚动条
document.removeEventListener('touchmove', mo, false);
},
},
};
</script>
<style lang="less" scoped>
.zzh_box_canvas {
.zzh_top {
padding-bottom: 5px;
}
}
#board {
// height: 300px;
position: relative;
display: inline-block;
border: 2px dashed #cccccc;
overflow: hidden;
box-sizing: border-box;
line-height: 0;
.cleanUp {
position: absolute;
right: 85px;
bottom: 30px;
width: 100px;
// height: 40px;
// line-height: 40px;
color: #409eff;
font-size: 14px;
cursor: pointer;
text-align: center;
}
.cleanUp2 {
position: absolute;
right: 20px;
bottom: 30px;
width: 100px;
// height: 40px;
// line-height: 40px;
color: #409eff;
font-size: 14px;
cursor: pointer;
text-align: center;
}
}
</style>
3. 在需要的页面 import 然后component注册组件,直接进行使用
直接v-model使用,也可以input或者change监听事件