字玩FontPlayer开发笔记2 钢笔工具优化
字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:
github: https://github.com/HiToysMaker/fontplayer
gitee: https://gitee.com/toysmaker/fontplayer
笔记
今天笔者发现原先的钢笔工具逻辑有些问题,进行了修复和优化。
笔者原先的钢笔工具,鼠标按下创建第一个锚点后,会自动生成第一个锚点对应的控制点,鼠标再次移动后,直接创建下一个锚点,而不能拖动自由选择控制点位置。只有后续锚点,才能拖动自由创建对应控制点。另外闭合路径时,没有自动延切线与第一条贝塞尔曲线进行连接,体验不是很好。原先效果如下图所示:
参考其他成熟软件的钢笔工具,做了以下两点优化:
- 创建第一个锚点时可以拖动创建控制点
关键代码:
// 长按鼠标
if (editAnchor.index === 0) {
//第一个锚点
let _anchor = _points[editAnchor.index]
let _control = _points[editAnchor.index + 1]
//将第一个锚点对应的控制点设置为鼠标移动位置
_control.x = getCoord(e.offsetX)
_control.y = getCoord(e.offsetY)
_control.isShow = true
} else {
// 后续锚点
let _anchor = _points[editAnchor.index]
let _control1 = _points[editAnchor.index - 1]
let _control2 = _points[editAnchor.index + 1]
//将锚点对应的后续控制点设置为鼠标移动位置
_control2.x = getCoord(e.offsetX)
_control2.y = getCoord(e.offsetY)
_control2.isShow = true
//将锚点对应的前接控制点设置为与后续控制点对称的位置
_control1.x = _anchor.x - (getCoord(e.offsetX) - _anchor.x)
_control1.y = _anchor.y - (getCoord(e.offsetY) - _anchor.y)
_control1.isShow = true
}
- 闭合路径时如果用户不做拖动控制点操作,自动计算控制点切线,使最后一条贝塞尔与第一条贝塞尔曲线延切线连接
关键代码:
// 当鼠标移动至第一个锚点所在位置附近时,自动闭合路径
if (isNearPoint(getCoord(e.offsetX), getCoord(e.offsetY), points.value[0].x, points.value[0].y, nearD)) {
// 将最后一个锚点位置设置为第一个锚点位置
_anchor.x = points.value[0].x
_anchor.y = points.value[0].y
// 自动延切线与第一条贝塞尔曲线进行连接
_control2.x = points.value[1].x
_control2.y = points.value[1].y
_control1.x = points.value[0].x - (points.value[1].x - points.value[0].x)
_control1.y = points.value[0].y - (points.value[1].y - points.value[0].y)
closePath = true
}
优化后,效果如下图所示:
优化后,移动鼠标监听事件的完整代码:
const onMouseMove = (e: MouseEvent) => {
const _points = R.clone(points.value)
if (mousedown) {
_points[_controlIndex] = _lastControl
// 长按鼠标
if (editAnchor.index === 0) {
//第一个锚点
let _anchor = _points[editAnchor.index]
let _control = _points[editAnchor.index + 1]
//将第一个锚点对应的控制点设置为鼠标移动位置
_control.x = getCoord(e.offsetX)
_control.y = getCoord(e.offsetY)
_control.isShow = true
} else {
// 后续锚点
let _anchor = _points[editAnchor.index]
let _control1 = _points[editAnchor.index - 1]
let _control2 = _points[editAnchor.index + 1]
//将锚点对应的后续控制点设置为鼠标移动位置
_control2.x = getCoord(e.offsetX)
_control2.y = getCoord(e.offsetY)
_control2.isShow = true
//将锚点对应的前接控制点设置为与后续控制点对称的位置
_control1.x = _anchor.x - (getCoord(e.offsetX) - _anchor.x)
_control1.y = _anchor.y - (getCoord(e.offsetY) - _anchor.y)
_control1.isShow = true
}
mousemove = true
setPoints(_points)
}
if (!mousedown) {
if (!mousemove) {
// 第一次移动鼠标
_lastControl = Object.assign({}, _points[_points.length - 1])
_controlIndex = _points.length - 1
const _anchor = {
uuid: genUUID(),
type: 'anchor',
x: getCoord(e.offsetX),
y: getCoord(e.offsetY),
origin: null,
isShow: true,
}
const _control1 = {
uuid: genUUID(),
type: 'control',
x: _anchor.x,
y: _anchor.y,
origin: _anchor.uuid,
isShow: false,
}
const _control2 = {
uuid: genUUID(),
type: 'control',
x: _anchor.x,
y: _anchor.y,
origin: _anchor.uuid,
isShow: false,
}
_points.push(_control1, _anchor, _control2)
setPoints(_points)
mousemove = true
} else {
// 移动鼠标
_controlIndex = _points.length - 4
const _anchor = _points[_points.length - 2]
const _control1 = _points[_points.length - 3]
const _control2 = _points[_points.length - 1]
_anchor.x = getCoord(e.offsetX)
_anchor.y = getCoord(e.offsetY)
closePath = false
// 当鼠标移动至第一个锚点所在位置附近时,自动闭合路径
if (isNearPoint(getCoord(e.offsetX), getCoord(e.offsetY), points.value[0].x, points.value[0].y, nearD)) {
// 将最后一个锚点位置设置为第一个锚点位置
_anchor.x = points.value[0].x
_anchor.y = points.value[0].y
// 自动延切线与第一条贝塞尔曲线进行连接
_control2.x = points.value[1].x
_control2.y = points.value[1].y
_control1.x = points.value[0].x - (points.value[1].x - points.value[0].x)
_control1.y = points.value[0].y - (points.value[1].y - points.value[0].y)
closePath = true
}
setPoints(_points)
mousemove = true
}
}
}