前言
为了追踪所画内容,诸如画图应用程序、计算机辅助设计系统(computer-aided design system 简称CAD系统)以及游戏等许多应用程序,都会维护一份包含当前显示对象的列表。通常来说,这些应用程序都允许用户对当前显示在屏幕上的物体进行操作。比方说,在CAD应用程序中,我们可以对设计中的元素进行选择、移动、缩放等操作
——《HTML5 Canvas核心技术》
在Canvas中也同样如此,Canvas提供了一个名为isPointInPath(x, y)
的API,判断点(x, y)
是否在路径之中。如果在路径之中,则返回true。于是我们可以有如下思路:
维护一个可以描述各个路径的
数组
,通过ispointInPath(x, y)
判断点击位置是否在某一个路径之中,选中此路径,进行操作(移动、缩放等),再绘制图形
此前有同类型例子:Canvas高级路径操作之拖拽对象
本文记录如何使用Canvas拖拽一条贝塞尔曲线并进行编辑,例子来源于《HTML5 Canvas核心技术》,修改了部分代码,也放在CodePen
之中
Demo如下(印子是录屏软件的原因👹):

本文内容如下:
- 思路讲解
- ”橡皮带(rubber band)“与贝塞尔曲线
- 实现逻辑思路
- 伪代码思路
- 代码分割
- mousedown
- mousemove
- mouseup
- 结语
- 参考资料
思路讲解
思路奖讲解主要分为三部分,即”橡皮带(rubber band)“与贝塞尔曲线
、实现逻辑思路
、伪代码思路
”橡皮带(rubber band)“与贝塞尔曲线:阐述绘制Demo中如何贝塞尔曲线
实现逻辑思路:本Demo既有绘制
和编辑
两种状态
伪代码思路:结合伪代码阐述思路
”橡皮带(rubber band)“与贝塞尔曲线
首先我们看下三次贝塞尔曲线的APIvoid ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
cp1与cp2代表两个控制点,x、y代表另一个端点的位置,还有一个端点用moveTo()
作为起始位置即可
接下来是如何绘制

rubberBandRect
进行绘图,利用left、top、width、height
来定位四个点
无论是绘制曲线
还是编辑曲线
,都是通过改变rubberBandRect
,再绘制对应的点及曲线
实现逻辑思路
此阶段为编辑状态
,发生在绘制完曲线后,即有点可拖拽:

此状态为可绘制状态
,在初始状态或结束编辑状态之后(本Demo未做编辑”已定形曲线“的功能):

综上及Demo体验,我们可以把逻辑归纳为如下:

伪代码思路
这部分结合代码中部分关键变量
及思维导图进行说明:
关键变量:
editing: //是否处于编辑状态
drawingCurve: //是否正在绘制曲线
draggingPoint: //记录拖拽点,可能的值有
//null(表示绘制曲线后点击空白部分,无拖拽点,结束编辑阶段),
//endPoint,controlPoint

代码分割
mousedown
canvas.onmousedown = function (e) {
const loc = windowToCanvas(e.clientX, e.clientY);//获取位置
e.preventDefault();
if (editing === false) { //画线
saveDrawingSurface();
mousedown.x = loc.x;
mousedown.y = loc.y;
updateRubberBandRectangle(loc);//更新rubberBandRect
drawingCurve = true;//标记正在绘图
} else {//编辑曲线阶段进入
// 选择点击位置对应的点,三种可能null, endPoint, controlPoint
draggingPoint = cursorInControlPoint(loc);
if (draggingPoint === null) {
draggingPoint = cursorInEndPoint(loc);
}
}
}
mousedown主要判断是绘制曲线
还是编辑曲线
mousemove
canvas.onmousemove = function (e) {
const loc = windowToCanvas(e.clientX, e.clientY);
if (drawingCurve || draggingPoint) {//未使用应用情况下鼠标移动干扰
e.preventDefault();
restoreDrawingSurface();
if (guideWires) {
drawGuideWires(loc.x, loc.y);
}
}
if (drawingCurve) {//绘制曲线
updateRubberBand(loc); //更新rubberBand并绘制
drawControlAndEndPoints();
} else if (draggingPoint) {//mousedown编辑阶段中点击端点或控制点,开始拖拽
updateDraggingPoint(loc);
drawControlAndEndPoints();
drawBezierCurve();
}
}
mousemove主要排除不使用功能时鼠标滑过的情况,及对应阶段的绘制
mouseup
canvas.onmouseup = function (e) {
const loc = windowToCanvas(e.clientX, e.clientY);
restoreDrawingSurface();
if (editing === false) {//画线阶段进入
updateRubberBand(loc);
drawControlAndEndPoints();
drawingCurve = false;
editing = true; //mouseup后可进入编辑曲线阶段
} else {//编辑曲线阶段进入
if (draggingPoint) {//有拖拽点,进行拖拽
drawControlAndEndPoints();
} else {//没拖拽点,后续结束编辑状态
editing = false;
}
drawBezierCurve();
draggingPoint = null;
}
}
mouseup主要是对每个阶段的后续处理,如果是画线阶段结束,则进入编辑阶段
,如果是编辑阶段结束,则通过判断有无拖拽点继续编辑或结束编辑
结语
本例子只要理清了RubberBandRect
及绘制阶段与编辑阶段的控制
后就会容易很多了
不足之处欢迎大家交流学习✨
参考资料
《HTML5 Canvas核心技术》