<script>
|
export default {
|
beforeMount() {
|
window.addEventListener('resize', this.$_resizeHandler);
|
},
|
beforeUnmount() {
|
window.removeEventListener('resize', this.$_resizeHandler);
|
},
|
emits: ['drawEnd', 'update:bgColor'],
|
props: {
|
bgColor: {
|
default: '',
|
type: String,
|
},
|
format: {
|
default: 'image/png',
|
type: String,
|
},
|
height: {
|
default: 300,
|
type: Number,
|
},
|
isClearBgColor: {
|
default: true,
|
type: Boolean,
|
},
|
isCrop: {
|
default: false,
|
type: Boolean,
|
},
|
lineColor: {
|
default: '#000000',
|
type: String,
|
},
|
lineWidth: {
|
default: 4,
|
type: Number,
|
},
|
quality: {
|
default: 1,
|
type: Number,
|
},
|
width: {
|
default: 800,
|
type: Number,
|
},
|
},
|
data() {
|
return {
|
hasDrew: false,
|
resultImg: '',
|
points: [],
|
canvasTxt: null,
|
startX: 0,
|
startY: 0,
|
isDrawing: false,
|
sratio: 1,
|
};
|
},
|
computed: {
|
ratio() {
|
return this.height / this.width;
|
},
|
stageInfo() {
|
return this.$refs.canvas.getBoundingClientRect();
|
},
|
myBg() {
|
return this.bgColor ? this.bgColor : 'rgba(255, 255, 255, 0)';
|
},
|
},
|
watch: {
|
myBg(newVal) {
|
this.$refs.canvas.style.background = newVal;
|
},
|
},
|
methods: {
|
$_resizeHandler() {
|
const canvas = this.$refs.canvas;
|
if (!canvas) return;
|
canvas.style.width = `${this.width}px`;
|
const realw = Number.parseFloat(window.getComputedStyle(canvas).width);
|
canvas.style.height = `${this.ratio * realw}px`;
|
this.canvasTxt = canvas.getContext('2d');
|
this.canvasTxt.scale(1 * this.sratio, 1 * this.sratio);
|
this.sratio = realw / this.width;
|
this.canvasTxt.scale(1 / this.sratio, 1 / this.sratio);
|
},
|
drawEnd(obj) {
|
this.canvasTxt.beginPath();
|
this.canvasTxt.moveTo(this.startX, this.startY);
|
this.canvasTxt.lineTo(obj.x, obj.y);
|
this.canvasTxt.lineCap = 'round';
|
this.canvasTxt.lineJoin = 'round';
|
this.canvasTxt.stroke();
|
this.canvasTxt.closePath();
|
this.points.push(obj, { x: -1, y: -1 });
|
this.$emit('drawEnd');
|
},
|
drawMove(obj) {
|
this.canvasTxt.beginPath();
|
this.canvasTxt.moveTo(this.startX, this.startY);
|
this.canvasTxt.lineTo(obj.x, obj.y);
|
this.canvasTxt.strokeStyle = this.lineColor;
|
this.canvasTxt.lineWidth = this.lineWidth * this.sratio;
|
this.canvasTxt.lineCap = 'round';
|
this.canvasTxt.lineJoin = 'round';
|
this.canvasTxt.stroke();
|
this.canvasTxt.closePath();
|
this.startY = obj.y;
|
this.startX = obj.x;
|
this.points.push(obj);
|
},
|
// 绘制
|
drawStart(obj) {
|
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.lineCap = 'round';
|
this.canvasTxt.lineJoin = 'round';
|
this.canvasTxt.lineWidth = this.lineWidth * this.sratio;
|
this.canvasTxt.stroke();
|
this.canvasTxt.closePath();
|
this.points.push(obj);
|
},
|
// 操作
|
generate(options) {
|
const imgFormat = options && options.format ? options.format : this.format;
|
const imgQuality = options && options.quality ? options.quality : this.quality;
|
const pm = new Promise((resolve, reject) => {
|
if (!this.hasDrew) {
|
reject(new Error('Warning: Not Signned!'));
|
return;
|
}
|
const resImgData = this.canvasTxt.getImageData(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
|
this.canvasTxt.globalCompositeOperation = 'destination-over';
|
this.canvasTxt.fillStyle = this.myBg;
|
this.canvasTxt.fillRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
|
this.resultImg = this.$refs.canvas.toDataURL(imgFormat, imgQuality);
|
let resultImg = this.resultImg;
|
this.canvasTxt.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
|
this.canvasTxt.putImageData(resImgData, 0, 0);
|
this.canvasTxt.globalCompositeOperation = 'source-over';
|
if (this.isCrop) {
|
const crop_area = this.getCropArea(resImgData.data);
|
let crop_canvas = document.createElement('canvas');
|
const crop_ctx = crop_canvas.getContext('2d');
|
crop_canvas.width = crop_area[2] - crop_area[0];
|
crop_canvas.height = crop_area[3] - crop_area[1];
|
const crop_imgData = this.canvasTxt.getImageData(...crop_area);
|
crop_ctx.globalCompositeOperation = 'destination-over';
|
crop_ctx.putImageData(crop_imgData, 0, 0);
|
crop_ctx.fillStyle = this.myBg;
|
crop_ctx.fillRect(0, 0, crop_canvas.width, crop_canvas.height);
|
resultImg = crop_canvas.toDataURL(imgFormat, imgQuality);
|
crop_canvas = null;
|
}
|
resolve(resultImg);
|
});
|
return pm;
|
},
|
getCropArea(imgData) {
|
let topX = this.$refs.canvas.width;
|
let btmX = 0;
|
let topY = this.$refs.canvas.height;
|
let btnY = 0;
|
for (let i = 0; i < this.$refs.canvas.width; i++) {
|
for (let j = 0; j < this.$refs.canvas.height; j++) {
|
const pos = (i + this.$refs.canvas.width * j) * 4;
|
if (imgData[pos] > 0 || imgData[pos + 1] > 0 || imgData[pos + 2] || imgData[pos + 3] > 0) {
|
btnY = Math.max(j, btnY);
|
btmX = Math.max(i, btmX);
|
topY = Math.min(j, topY);
|
topX = Math.min(i, topX);
|
}
|
}
|
}
|
topX++;
|
btmX++;
|
topY++;
|
btnY++;
|
const data = [topX, topY, btmX, btnY];
|
return data;
|
},
|
// pc
|
mouseDown(e) {
|
e = e || event;
|
e.preventDefault();
|
this.isDrawing = true;
|
this.hasDrew = true;
|
const obj = {
|
x: e.offsetX,
|
y: e.offsetY,
|
};
|
this.drawStart(obj);
|
},
|
mouseMove(e) {
|
e = e || event;
|
e.preventDefault();
|
if (this.isDrawing) {
|
const obj = {
|
x: e.offsetX,
|
y: e.offsetY,
|
};
|
this.drawMove(obj);
|
}
|
},
|
mouseUp(e) {
|
e = e || event;
|
e.preventDefault();
|
const obj = {
|
x: e.offsetX,
|
y: e.offsetY,
|
};
|
this.drawEnd(obj);
|
this.isDrawing = false;
|
},
|
reset() {
|
this.canvasTxt.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
|
if (this.isClearBgColor) {
|
this.$emit('update:bgColor', '');
|
this.$refs.canvas.style.background = 'rgba(255, 255, 255, 0)';
|
}
|
this.points = [];
|
this.hasDrew = false;
|
this.resultImg = '';
|
},
|
touchEnd(e) {
|
e = e || event;
|
e.preventDefault();
|
if (e.touches.length === 1) {
|
const obj = {
|
x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
|
y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top,
|
};
|
this.drawEnd(obj);
|
}
|
},
|
touchMove(e) {
|
e = e || event;
|
e.preventDefault();
|
if (e.touches.length === 1) {
|
const obj = {
|
x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
|
y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top,
|
};
|
this.drawMove(obj);
|
}
|
},
|
// mobile
|
touchStart(e) {
|
e = e || event;
|
e.preventDefault();
|
this.hasDrew = true;
|
if (e.touches.length === 1) {
|
const obj = {
|
x: e.targetTouches[0].clientX - this.$refs.canvas.getBoundingClientRect().left,
|
y: e.targetTouches[0].clientY - this.$refs.canvas.getBoundingClientRect().top,
|
};
|
this.drawStart(obj);
|
}
|
},
|
},
|
mounted() {
|
const canvas = this.$refs.canvas;
|
canvas.height = this.height;
|
canvas.width = this.width;
|
canvas.style.background = this.myBg;
|
this.$_resizeHandler();
|
// 在画板以外松开鼠标后冻结画笔
|
document.addEventListener('mouseup', () => {
|
this.isDrawing = false;
|
});
|
},
|
};
|
</script>
|
|
<template>
|
<canvas
|
ref="canvas"
|
willReadFrequency
|
@mousedown="mouseDown"
|
@mousemove="mouseMove"
|
@mouseup="mouseUp"
|
@touchend="touchEnd"
|
@touchmove="touchMove"
|
@touchstart="touchStart"></canvas>
|
</template>
|
|
<style scoped>
|
canvas {
|
display: block;
|
max-width: 100%;
|
}
|
</style>
|