Mô tả:
Bảng vẽ sẽ hiển thị dữ liệu từ phía backend, người dùng có thể thêm hoặc xóa các đường. Khi chọn một đường để xóa, nó sẽ được tô sáng và có thể bị xóa bằng cách nhấp đúp chuột.
HTML:
<div id="app">
<canvas
id="drawingCanvas"
width="600px"
height="380px"
class="canvas"
@mousedown="startDrawing($event)"
@mousemove="drawLine($event)"
@mouseup="finishDrawing()"
@click="selectLine($event)"
@dblclick="removeLine($event)">
</canvas>
<input type="button" class="pen" value="Bút chì" @click="setMode('pen')">
<input type="button" class="clearCanvas" value="Xóa màn hình" @click="clearCanvas()">
<input type="button" class="eraser" value="Tẩy" @click="setMode('eraser')">
</div>
JavaScript:
new Vue({
el: "#app",
data: {
baseLines: [
{ idx: 0, x: 10, y: 300 },
{ idx: 1, x: 310, y: 300 }
],
shapes: [
{ "idx": 0, "x": 10, "y": 300 },
{ "idx": 1, "x": 70, "y": 230 },
{ "idx": 2, "x": 130, "y": 150 },
{ "idx": 3, "x": 190, "y": 150 },
{ "idx": 4, "x": 250, "y": 230 },
{ "idx": 5, "x": 310, "y": 300 }
],
intersections: [{ x: null, y: null, points: [{ idx: 1, x: 111, y: 111 }, { idx: 2, x: 233, y: 323 }, { idx: 3, x: 422, y: 435 }] }],
tempPoints: [],
isDrawing: false,
currentIndex: 0,
toolType: "",
selectedShapeIndex: -1
},
mounted() {
this.drawBase(this.baseLines);
this.renderShapes(this.intersections);
},
methods: {
setMode(tool) {
this.toolType = tool;
},
clearCanvas() {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.drawBase(this.baseLines);
this.renderShapes(this.shapes);
},
drawBase(lines) {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
lines.forEach((line, index) => {
if (index === 0) {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(line.x, line.y);
} else {
ctx.lineTo(line.x, line.y);
ctx.stroke();
}
});
},
renderShapes(shapes) {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
shapes.forEach(shape => {
shape.points.forEach((point, i) => {
if (i === 0) {
ctx.beginPath();
ctx.strokeStyle = this.selectedShapeIndex === shape.idx ? 'red' : 'black';
ctx.moveTo(point.x, point.y);
} else {
ctx.lineTo(point.x, point.y);
ctx.stroke();
}
});
});
},
startDrawing(e) {
if (this.toolType === 'pen') {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x, y);
this.isDrawing = true;
this.tempPoints.push({ idx: this.currentIndex++, x, y });
}
},
drawLine(e) {
if (this.isDrawing && this.toolType === 'pen') {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const lastPoint = this.tempPoints[this.tempPoints.length - 1];
if (Math.sqrt(Math.pow(lastPoint.x - x, 2) + Math.pow(lastPoint.y - y, 2)) >= 5) {
ctx.lineTo(x, y);
ctx.stroke();
this.tempPoints.push({ idx: this.currentIndex++, x, y });
}
}
},
finishDrawing() {
if (this.toolType === 'pen' && this.isDrawing) {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
ctx.closePath();
if (this.tempPoints.length > 1) {
this.intersections.push({ points: this.tempPoints });
}
this.tempPoints = [];
this.isDrawing = false;
this.currentIndex = 0;
}
},
removeLine(e) {
if (this.toolType === 'eraser' && this.selectedShapeIndex !== -1) {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.intersections.splice(this.selectedShapeIndex, 1);
this.selectedShapeIndex = -1;
this.drawBase(this.baseLines);
this.renderShapes(this.shapes);
}
},
selectLine(e) {
if (this.toolType === 'eraser') {
const canvas = document.getElementById("drawingCanvas");
const ctx = canvas.getContext("2d");
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const point = { x, y };
const result = this.pointOnSegments(point, this.intersections);
if (result.onLine) {
this.selectedShapeIndex = result.index;
ctx.clearRect(0, 0, canvas.width, canvas.height);
this.drawBase(this.baseLines);
this.renderShapes(this.shapes);
}
}
},
pointOnSegments(p, segments) {
let onLine = false, index = -1;
segments.forEach((segment, segIdx) => {
segment.points.forEach((point, ptIdx) => {
if (ptIdx > 0) {
const prevPoint = segment.points[ptIdx - 1];
if (this.isOnSegment(prevPoint, point, p)) {
onLine = true;
index = segIdx;
return { onLine, index };
}
}
});
});
return { onLine, index };
},
isOnSegment(p1, p2, p) {
const minX = Math.min(p1.x, p2.x), maxX = Math.max(p1.x, p2.x);
const minY = Math.min(p1.y, p2.y), maxY = Math.max(p1.y, p2.y);
const offset = 10;
if (p1.y === p2.y) {
if ((p.x >= minX && p.x <= maxX) && (p.y >= minY - offset && p.y <= maxY + offset)) {
return true;
}
return false;
} else if (p1.x === p2.x) {
if ((p.y >= minY && p.y <= maxY) && (p.x >= minX - offset && p.x <= maxX + offset)) {
return true;
}
return false;
} else {
if ((p.x >= minX && p.x <= maxX) && (p.y >= minY - offset && p.y <= maxY + offset)) {
const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
const distance = (p.x - p1.x) / Math.cos(angle);
const yCoord = Math.sin(angle) * distance;
const crossPoint = { x: p.x, y: p1.y + yCoord };
if ((Math.abs(p.x - crossPoint.x) <= offset) && (Math.abs(p.y - crossPoint.y) <= offset)) {
return true;
}
}
return false;
}
}
}
});