本文会带大家使用 TypeScript 继续封装构造函数 CanvasContainer。以实现移动选中的直线、矩形、多边形的各个端点。
二、Canvas 画布:绘制直线、矩形、多边形 👉👉👉 代码演示
三、Canvas 画布:图形的选中和移动 (上) 👉👉👉 代码演示
四、Canvas 画布:图形的选中和移动 (下) 👉👉👉 代码演示
五、Canvas 画布:修改图形各端点的位置 👉👉👉 代码演示
六、Canvas 画布:图形移动时的辅助线和吸附效果 👉👉👉 代码演示
一、优化图形的数据结构
首先我们需要定义几个用到的变量。
class CanvasContainer {
/** 记录鼠标按下时,是否在图形的端点上,如果不在,则为 false;如果在,则记录是第几个端点 */
private isMoveCircle: false | number = false;
}
二、判断鼠标的位置
这一步,我们需要来判断鼠标的位置是不是在图形的端点上,如果鼠标的位置与图形的一个端点的距离 小于 20
,我们就认为当前的端点被选中。
两个点之间的距离的计算公式用:
我们创建如下函数,用来判断鼠标的位置:
/**
* 判断鼠标在那个图形的第几个端点内
* @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); }