Tạo bảng vẽ đơn giản với Vue và Canvas

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;
            }
        }
    }
});

Thẻ: Vue.js Canvas JavaScript Web Development

Đăng vào ngày 4 tháng 7 lúc 14:28