组件效果
代码
这个组件,项目里是用来框住文字,擦除水印的,把框住的坐标给后端,效果如图,具体看代码吧
<template>
<div class="erase-canvas">
<canvas
ref="eraseCanvas"
@mousedown="startDrawing"
@mousemove="moveDraw"
@mouseup="stopDrawing"
@mouseleave="leaveDrawing"
class="erase-canvas"
></canvas>
</div>
</template>
<script setup>
import { ref } from "vue";
const props = defineProps({
canvasWidth: {
type: Number,
default: 0,
},
canvasHeight: {
type: Number,
default: 0,
},
});
const eraseCanvas = ref(null);
const eraseCtx = ref(null);
const activeIndex = ref(0);
const eraseBoxes = ref([]);
let dx = 0;
let dy = 0;
let handleRadius = 10;
let activePointList = [];
let defaultWidth;
let defaultHeight;
// 初始化画布
const initCanvas = () => {
const canvas = eraseCanvas.value;
canvas.width = props.canvasWidth;
canvas.height = props.canvasHeight;
defaultWidth = canvas.width * 0.2;
defaultHeight = canvas.height * 0.1;
eraseCtx.value = canvas.getContext("2d");
const ctx = eraseCtx.value;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.fillStyle = '#dedede';
// ctx.fillRect(0, 0, canvas.width, canvas.height);
};
function windowToCanvas(x, y, rect) {
// 获取Canvas的边界矩形
const topCanvas = eraseCanvas.value;
// 计算缩放比例
const scaleX = topCanvas.width / rect.width;
const scaleY = topCanvas.height / rect.height;
// 转换为Canvas坐标
const canvasX = (x - rect.left) * scaleX;
const canvasY = (y - rect.top) * scaleY;
return { x: canvasX, y: canvasY };
}
// 检查点是否在矩形内
function isPointInRect(x, y) {
const box = eraseBoxes.value;
let index = -1;
for (let i = box.length - 1; i >= 0; i--) {
const rect = box[i];
// console.log(
// x >= rect.x &&
// x <= rect.x + rect.width &&
// y >= rect.y &&
// y <= rect.y + rect.height,
// x,
// y,
// box[i]
// );
if (
x >= rect.x &&
x <= rect.x + rect.width &&
y >= rect.y &&
y <= rect.y + rect.height
) {
activeIndex.value = i;
index = i;
break;
}
}
return index;
}
// 检测是否点击边框上的点,进行缩放
function isPointInScaleHandle(x, y) {
const obj = {
isIn: false,
pointIndex: -1,
};
const point = eraseBoxes.value[activeIndex.value];
activePointList = [
{ x: point.x, y: point.y },
{ x: point.x + point.width, y: point.y },
{ x: point.x + point.width, y: point.y + point.height },
{ x: point.x, y: point.y + point.height },
];
for (let i = 0; i < activePointList.length; i++) {
const dx = activePointList[i].x - x;
const dy = activePointList[i].y - y;
// console.log(point.x, x, point.y, y, "点");
if (dx * dx + dy * dy <= handleRadius * handleRadius) {
obj.isIn = true;
obj.pointIndex = i;
break;
}
}
return obj;
}
const draw = () => {
const canvas = eraseCanvas.value;
const ctx = eraseCtx.value;
// 清除Canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
const erase = eraseBoxes.value;
for (let i = 0; i < erase.length; i++) {
if (activeIndex.value === i) {
createRect(erase[i]);
} else {
createDashRect(erase[i]);
}
}
};
let pointIndexs = -1;
// 是否开始移到
let isDragging = false;
let isScale = false;
let lastX = 0;
let lastY = 0;
let oppositeCorner = null;
const startDrawing = (e) => {
const canvas = eraseCanvas.value;
const rect = canvas.getBoundingClientRect();
const { x, y } = windowToCanvas(e.clientX, e.clientY, rect);
const { isIn, pointIndex } = isPointInScaleHandle(x, y);
console.log(isIn, pointIndex);
if (isIn) {
// canvas.style.cursor = 'crosshair';
pointIndexs = pointIndex;
oppositeCorner = activePointList[(pointIndex + 2) % 4];
lastX = x;
lastY = y;
console.log("开始缩放");
isScale = true;
return;
}
const index = isPointInRect(x, y);
if (index > -1) {
// canvas.style.cursor = 'all-scroll';
isDragging = true;
console.log("开始拖动");
dx = x - eraseBoxes.value[index].x;
dy = y - eraseBoxes.value[index].y;
draw();
return;
}
};
const moveDraw = (e) => {
const canvas = eraseCanvas.value;
const rect = canvas.getBoundingClientRect();
const { x, y } = windowToCanvas(e.clientX, e.clientY, rect);
if (eraseBoxes.value.length) {
const { isIn } = isPointInScaleHandle(x, y);
if (isPointInRect(x, y) > -1) {
canvas.style.cursor = 'all-scroll';
} else if (isIn) {
canvas.style.cursor = 'crosshair';
} else {
canvas.style.cursor = 'default';
}
}
if (isDragging) {
eraseBoxes.value[activeIndex.value].x = x - dx;
eraseBoxes.value[activeIndex.value].y = y - dy;
// console.log("正在拖动");
draw();
}
if (isScale) {
let boxX = x - lastX;
let boxY = y - lastY;
// console.log("正在缩放", boxX);
const activeBox = eraseBoxes.value[activeIndex.value];
console.log("缩放中", pointIndexs);
// 计算对应点的旋转角度
switch (pointIndexs) {
case 0:
activeBox.width = Math.max(50, activeBox.width + boxX * -1);
activeBox.height = Math.max(50, activeBox.height + boxY * -1);
activeBox.x = oppositeCorner.x - activeBox.width
activeBox.y = oppositeCorner.y - activeBox.height
break;
case 1:
activeBox.width = Math.max(50, activeBox.width + boxX * 1);
activeBox.height = Math.max(50, activeBox.height + boxY * -1);
activeBox.y = oppositeCorner.y - activeBox.height
break;
case 2:
activeBox.width = Math.max(50, activeBox.width + boxX * 1);
activeBox.height = Math.max(50, activeBox.height + boxY * 1);
break;
case 3:
activeBox.x = oppositeCorner.x - activeBox.width
activeBox.width = Math.max(50, activeBox.width + boxX * -1);
activeBox.height = Math.max(50, activeBox.height + boxY * 1);
break;
}
lastX = x;
lastY = y;
draw();
}
};
const stopDrawing = (e) => {
isDragging = false;
isScale = false;
};
const leaveDrawing = (e) => {
isDragging = false;
isScale = false;
};
const addRect = (type) => {
// const rect = eraseCanvas.value.getBoundingClientRect();
const canvas = eraseCanvas.value;
// let len = eraseBoxes.value.length;
// 设置默认擦除框大小和位置
const newBox = {
type,
x: (canvas.width - defaultWidth) / 2,
y: (canvas.height - defaultHeight) / 2,
width: defaultWidth,
height: defaultHeight,
};
eraseBoxes.value.push(newBox);
// if (eraseBoxes.value.length === 0) {
// activeRect.value = { ...newBox }
// }
// createRect(newBox);
activeIndex.value = eraseBoxes.value.length - 1;
draw();
};
// 初始化变量
let dashLength = 15;
let gapLength = 10;
let lineWidth = 3;
// 画虚线矩形
const createDashRect = (box) => {
const ctx = eraseCtx.value;
// 绘制矩形
ctx.beginPath();
// 设置虚线样式
ctx.setLineDash([dashLength, gapLength]);
ctx.fillStyle = 'rgba(52, 152, 219, 0.2)';
ctx.fillRect(box.x, box.y, box.width, box.height);
ctx.lineWidth = lineWidth;
ctx.strokeStyle = "#3498db";
ctx.lineCap = "round";
ctx.rect(box.x, box.y, box.width, box.height);
ctx.closePath();
ctx.stroke();
// 重置虚线设置
ctx.setLineDash([]);
createText('文字擦除', box.x, box.y - 20)
};
// 画实线线矩形
const createRect = (box) => {
// console.log("createRect", box);
const ctx = eraseCtx.value;
// 绘制矩形
ctx.beginPath();
// ctx.moveTo(box.x, box.y);
// ctx.lineTo(box.x + box.width, box.y);
// ctx.lineTo(box.x + box.width, box.y + box.height);
// ctx.lineTo(box.x, box.y + box.height);
// 设置样式
ctx.lineWidth = 3;
ctx.fillStyle = 'rgba(52, 152, 219, 0.2)';
ctx.fillRect(box.x, box.y, box.width, box.height);
ctx.strokeStyle = "rgba(52, 152, 219, 1)";
ctx.rect(box.x, box.y, box.width, box.height);
ctx.stroke();
createPoint(box);
createText('文字擦除', box.x, box.y - 20)
};
const createPoint = (box) => {
const ctx = eraseCtx.value;
const handleRadius = 10; // 边框上的点半径
ctx.beginPath();
ctx.arc(box.x, box.y, handleRadius, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.beginPath();
ctx.arc(box.x + box.width, box.y, handleRadius, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.beginPath();
ctx.arc(box.x + box.width, box.y + box.height, handleRadius, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.beginPath();
ctx.arc(box.x, box.y + box.height, handleRadius, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
};
// 绘制文本
const createText = (text, x, y) => {
drawRoundedRect(x, y - 20, 80, 30, 4)
// ctx.save()
const ctx = eraseCtx.value;
ctx.fillStyle = "white";
ctx.font = "15px Arial";
ctx.fillText(text, x + 10, y);
// ctx.fill();
};
// 绘制圆角矩形
function drawRoundedRect(x, y, width, height, radius) {
const ctx = eraseCtx.value;
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
}
const removeRect = (index) => {
eraseBoxes.value.splice(index, 1);
if (activeIndex.value === index) {
activeIndex.value = activeIndex.value.length - 1;
}
draw();
};
const editEraseBox = (index) => {
activeIndex.value = index
draw();
}
const resetCanvas = () => {
const ctx = eraseCtx.value;
if (!ctx) return
const canvas = eraseCanvas.value
ctx.clearRect(0, 0, canvas.width, canvas.height);
eraseBoxes.value = []
activeIndex.value = 0
}
defineExpose({
initCanvas,
eraseBoxes,
addRect,
removeRect,
editEraseBox,
resetCanvas
});
</script>
<style scoped lang="scss">
.erase-canvas {
position: absolute;
height: 100%;
width: 100%;
top: 0;
left: 0;
z-index: 99;
.erase-canvas {
height: 100%;
width: 100%;
}
}
</style>