Canvas 画布:修改图形各端点的位置

222 阅读3分钟

本文会带大家使用 TypeScript 继续封装构造函数 CanvasContainer。以实现移动选中的直线、矩形、多边形的各个端点。

一、Canvas 画布:拖动和缩放 👉👉👉 代码演示

二、Canvas 画布:绘制直线、矩形、多边形 👉👉👉 代码演示

三、Canvas 画布:图形的选中和移动 (上) 👉👉👉 代码演示

四、Canvas 画布:图形的选中和移动 (下) 👉👉👉 代码演示

五、Canvas 画布:修改图形各端点的位置 👉👉👉 代码演示

六、Canvas 画布:图形移动时的辅助线和吸附效果 👉👉👉 代码演示

一、优化图形的数据结构

首先我们需要定义几个用到的变量。

class CanvasContainer {
    /** 记录鼠标按下时,是否在图形的端点上,如果不在,则为 false;如果在,则记录是第几个端点 */
    private isMoveCircle: false | number = false;
}

二、判断鼠标的位置

这一步,我们需要来判断鼠标的位置是不是在图形的端点上,如果鼠标的位置与图形的一个端点的距离 小于 20,我们就认为当前的端点被选中。

两个点之间的距离的计算公式用: (x1+y1)2+(x2+y2)2\sqrt {(x_1 + y_1)^2 + (x_2 + y_2)^2}

我们创建如下函数,用来判断鼠标的位置:

/**
 * 判断鼠标在那个图形的第几个端点内
 * @param x 鼠标的 X 坐标
 * @param y 鼠标的 Y 坐标
 */
pointInControlCircle = (x: number, y: number): false | number => {
    const mousePoint = {
        x: (-this.offsetX + x) / this.scale,
        y: (-this.offsetY + y) / this.scale,
    };
    // 判断鼠标是否在图形的端点上,如果不在,则为 false;如果在,则记录是第几个端点
    let isPoint: boolean | number = false;
    for (let i = 0; i < this.graphs.length; i++) {
        // 只计算在画布可视区内的图形
        if (this.graphs[i].select) {
            for(let j = 0; j < this.graphs[i].points.length; j++) {
                // 计算点到点的距离
                const interval = Math.sqrt(
                    Math.pow(this.graphs[i].points[j].x - mousePoint.x, 2)
                    + 
                    Math.pow(this.graphs[i].points[j].y - mousePoint.y, 2)
                );
                if (interval < 20) {
                    this.selectGraph = this.graphs[i];  // 将当前图形记录为选中图形
                    isPoint = j;                        // 记录当前的端点是第几个端点
                    break;
                }
            }
            if (isPoint !== false) break;
        }
    }
    return isPoint;
}

三、绘制拖拽的辅助点

这一步,我们需要为要移动的端点做一些标记,以便于我们更好的去移动端点。为此,我们做两件事:

  • 第一件:在图形可以移动的端点画一个圆形,用来表示这个端点可以移动。

    if (selectGraph.select) {
        selectGraph.points.forEach((point, index) => {
            this.ctx.beginPath();
            // 给选中的图形可移动的点,绘制一个红色的圆
            this.ctx.arc(point.x, point.y, 5, 0, 2 * Math.PI);
            this.ctx.closePath();
            // 如果当前的端点在鼠标下,则绘制实心圆
            if (this.isMoveCircle === index) {
                this.ctx.fill();
            }
            // 如果当前的端点在鼠标下,则绘制空心圆
            else {
                this.ctx.stroke();
            }
        })
    }
    
  • 第二件:当鼠标移动至可移动端点 20 个像素内鼠标的样式变化为小手的形状,表示当前位置可以移动。

    /** 鼠标移入或移出图形时的样式 */
    private onMouseMove = (event: MouseEvent) => {
        if (!this.isMoveGraph) return;
    
        this.log_move && console.time('move')
        const selectGraph = this.pointInGraph(event.x, event.y);
        // 判断鼠标是否在图形的端点上
        const isPoint = this.pointInControlCircle(event.x, event.y);
        this.log_move && console.timeEnd('move')
        
        // 如果在,则变更鼠标样式
        if (typeof isPoint !== 'boolean') {
            this.canvas.style.cursor = 'grab';
        }
        else if (selectGraph) {
            this.canvas.style.cursor = 'move';
        }
        else {
            this.canvas.style.cursor = 'default';
        }
    }
    

四、移动图形端点

移动图形的端点,首先我们需要记录鼠标按下时,鼠标选中的图形的端点的信息,然后修改所选中的这个图形的端点在鼠标移动时的位置。

  • 鼠标按下时
    // 移动图形
    if (this.isMoveGraph) {
        ......
        // 记录选中图形的位置信息和端点选中信息
        this.isMoveCircle = this.pointInControlCircle(event.x, event.y);
        ......
    }
    
  • 鼠标移动时
    // 当鼠标按下移动时,按下的位置上有可移动的端点
    if (typeof this.isMoveCircle !== 'boolean' && this.selectGraph) {
        this.selectGraph.points = this.mouseDownSelectGraphInfo?.points?.map((point, index) => {
            // 只移动选中的端点
            if (index === this.isMoveCircle) {
                return {
                    x: point.x + (event.x - this.mouseDownOffsetX) / this.scale,
                    y: point.y + (event.y - this.mouseDownOffsetY) / this.scale,
                }
            }
            else {
                return point;
            }
        }) as PointsType;
        // 重新渲染
        this.renderActive(this.selectGraph);
    }