vue+canvas实现按下鼠标绘制箭头

需要实现的效果:

直接上代码:

html 复制代码
<template>
    <div class="arrow-canvas-container">
        <canvas 
            ref="canvas"
            class="arrow-canvas"
            :width="canvasWidth"
            :height="canvasHeight"
            @mousedown="handleMouseDown"
            @mousemove="handleMouseMove"
            @mouseup="handleMouseUp"
            @touchstart="handleTouchStart"
            @touchmove="handleTouchMove"
            @touchend="handleTouchEnd"
        ></canvas>
    </div>
</template>

<script>
export default {
    name: 'ArrowCanvas',
    emits: ['arrow-drawn', 'canvas-cleared'],
    props: {
        width: {
            type: Number,
            default: 800
        },
        height: {
            type: Number,
            default: 600
        },
        lineColor: {
            type: String,
            default: '#3b82f6'
        },
        lineWidth: {
            type: Number,
            default: 3
        },
        arrowStyle: {
            type: String,
            default: 'single',
            validator: (value) => ['single', 'double'].includes(value)
        },
        showGrid: {
            type: Boolean,
            default: false
        }
    },
    data() {
        return {
            canvas: null,
            ctx: null,
            isDrawing: false,
            startPoint: null,
            endPoint: null,
            arrows: []
        }
    },
    computed: {
        canvasWidth() {
            return this.width;
        },
        canvasHeight() {
            return this.height;
        }
    },
    mounted() {
        this.canvas = this.$refs.canvas;
        this.ctx = this.canvas.getContext('2d');
        this.initCanvas();
    },
    methods: {
        initCanvas() {
            this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
            if (this.showGrid) {
                this.drawGrid();
            }
        },

        handleMouseDown(event) {
            this.startDrawing(this.getCanvasCoordinates(event));
        },

        handleMouseMove(event) {
            this.drawing(this.getCanvasCoordinates(event));
        },

        handleMouseUp(event) {
            this.stopDrawing(this.getCanvasCoordinates(event));
        },

        handleTouchStart(event) {
            event.preventDefault();
            const touch = event.touches[0];
            this.startDrawing(this.getCanvasCoordinates(touch));
        },

        handleTouchMove(event) {
            event.preventDefault();
            const touch = event.touches[0];
            this.drawing(this.getCanvasCoordinates(touch));
        },

        handleTouchEnd(event) {
            event.preventDefault();
            this.stopDrawing();
        },

        getCanvasCoordinates(event) {
            const rect = this.canvas.getBoundingClientRect();
            return {
                x: event.clientX - rect.left,
                y: event.clientY - rect.top
            };
        },

        startDrawing(point) {
            this.isDrawing = true;
            this.startPoint = point;
            this.endPoint = point;
        },

        drawing(point) {
            if (!this.isDrawing || !this.startPoint) return;

            this.endPoint = point;
            this.redraw();
        },

        stopDrawing() {
            if (this.isDrawing && this.startPoint && this.endPoint) {
                const arrow = {
                    start: { ...this.startPoint },
                    end: { ...this.endPoint },
                    color: this.lineColor,
                    width: this.lineWidth,
                    style: this.arrowStyle,
                    timestamp: Date.now()
                };

                this.arrows.push(arrow);
                this.$emit('arrow-drawn', arrow);
            }

            this.isDrawing = false;
            this.startPoint = null;
            this.endPoint = null;
        },

        redraw() {
            this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
            
            if (this.showGrid) {
                this.drawGrid();
            }
            
            this.arrows.forEach(arrow => {
                this.drawSingleArrow(arrow);
            });
            
            if (this.isDrawing && this.startPoint && this.endPoint) {
                this.drawSingleArrow({
                    start: this.startPoint,
                    end: this.endPoint,
                    color: this.lineColor,
                    width: this.lineWidth,
                    style: this.arrowStyle
                });
            }
        },

        drawSingleArrow(arrow) {
            const { start, end, color, width, style } = arrow;
            const headLength = 15;
            const angle = Math.atan2(end.y - start.y, end.x - start.x);
            
            this.ctx.save();
            this.ctx.strokeStyle = color;
            this.ctx.fillStyle = color;
            this.ctx.lineWidth = width;
            this.ctx.lineCap = 'round';
            
            // 绘制线条
            this.ctx.beginPath();
            this.ctx.moveTo(start.x, start.y);
            this.ctx.lineTo(end.x, end.y);
            this.ctx.stroke();
            
            // 绘制箭头头部
            this.ctx.beginPath();
            this.ctx.moveTo(end.x, end.y);
            this.ctx.lineTo(
                end.x - headLength * Math.cos(angle - Math.PI / 6),
                end.y - headLength * Math.sin(angle - Math.PI / 6)
            );
            this.ctx.lineTo(
                end.x - headLength * Math.cos(angle + Math.PI / 6),
                end.y - headLength * Math.sin(angle + Math.PI / 6)
            );
            this.ctx.closePath();
            this.ctx.fill();
            
            // 双箭头样式
            if (style === 'double') {
                this.ctx.beginPath();
                this.ctx.moveTo(start.x, start.y);
                this.ctx.lineTo(
                    start.x + headLength * Math.cos(angle - Math.PI / 6),
                    start.y + headLength * Math.sin(angle - Math.PI / 6)
                );
                this.ctx.lineTo(
                    start.x + headLength * Math.cos(angle + Math.PI / 6),
                    start.y + headLength * Math.sin(angle + Math.PI / 6)
                );
                this.ctx.closePath();
                this.ctx.fill();
            }
            
            this.ctx.restore();
        },

        drawGrid() {
            this.ctx.strokeStyle = 'rgba(0, 0, 0, 0.08)';
            this.ctx.lineWidth = 1;
            
            const gridSize = 20;
            
            // 垂直线
            for (let x = 0; x <= this.canvasWidth; x += gridSize) {
                this.ctx.beginPath();
                this.ctx.moveTo(x, 0);
                this.ctx.lineTo(x, this.canvasHeight);
                this.ctx.stroke();
            }
            
            // 水平线
            for (let y = 0; y <= this.canvasHeight; y += gridSize) {
                    this.ctx.beginPath();
                    this.ctx.moveTo(0, y);
                    this.ctx.lineTo(this.canvasWidth, y);
                    this.ctx.stroke();
                }
            },

            clearCanvas() {
                this.arrows = [];
                this.initCanvas();
                this.$emit('canvas-cleared');
        },

        undo() {
            if (this.arrows.length > 0) {
                this.arrows.pop();
                this.redraw();
            }
        },

        exportImage() {
                return this.canvas.toDataURL('image/png');
            }
        },

        watch: {
            lineColor() {
                this.redraw();
            },
            lineWidth() {
                this.redraw();
            },
            arrowStyle() {
                this.redraw();
            },
            showGrid() {
                this.redraw();
            }
        }
    };
</script>

<style scoped>
.arrow-canvas-container {
    position: relative;
    width: 100%;
    height: 100%;
}

.arrow-canvas {
    border: 2px dashed #d1d5db;
    border-radius: 12px;
    background-color: white;
    cursor: crosshair;
    transition: all 0.3s ease;
}

.arrow-canvas:hover {
    border-color: #3b82f6;
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
</style>

以上代码可以直接复制使用~

如有问题,欢迎留言讨论~

相关推荐
QuantumLeap丶1 小时前
《Flutter全栈开发实战指南:从零到高级》- 21 -响应式设计与适配
android·javascript·flutter·ios·前端框架
wordbaby1 小时前
组件与外部世界的桥梁:一文读懂 useEffect 的核心机制
前端·react.js
wordbaby1 小时前
永远不要欺骗 React:详解 useEffect 依赖规则与“闭包陷阱”
前端·react.js
火星数据-Tina1 小时前
体彩数据API
前端·websocket
源码方舟1 小时前
【华为云DevUI开发实战】
前端·vue.js·华为云
VOLUN1 小时前
封装通用可视化大屏布局组件:Vue3打造高复用性的 ChartFlex/ChartFlexItem
前端·vue.js
bug总结1 小时前
“RTMP 怎么在 Web 端最简单、最省事地播放?
前端
chilavert3181 小时前
技术演进中的开发沉思-228 Ajax: Aptana开发
前端·javascript·ajax
kwg1261 小时前
Dify二次开发-AI 应用端反馈指令接收(AI 应用端 → Dify)
前端·数据库·人工智能