您的位置:首页 > 新闻 > 会展 > 泰安建站哪家好_小企业如何建网站_企业网页设计制作_整站优化推广

泰安建站哪家好_小企业如何建网站_企业网页设计制作_整站优化推广

2025/2/26 7:25:06 来源:https://blog.csdn.net/toysmaker/article/details/145610729  浏览:    关键词:泰安建站哪家好_小企业如何建网站_企业网页设计制作_整站优化推广
泰安建站哪家好_小企业如何建网站_企业网页设计制作_整站优化推广

目录

  • 字玩FontPlayer开发笔记13 Vue3实现钢笔工具
      • 笔记
        • 钢笔工具的实现效果:
        • 钢笔组件的创建
          • 整体逻辑
          • 临时变量
          • 钢笔组件数据结构
          • 钢笔工具初始化
          • mousedown事件
          • mousemove事件
          • mouseup事件
          • 绘制钢笔组件控件
        • 钢笔组件的编辑
          • 整体逻辑
          • 临时变量
          • 编辑钢笔初始化
          • 事件监听器
          • 使用renderSelectPenEditor渲染控件,每次数值变化时,重新渲染

字玩FontPlayer开发笔记13 Vue3实现钢笔工具

字玩FontPlayer是笔者开源的一款字体设计工具,使用Vue3 + ElementUI开发,源代码:github | gitee

笔记

钢笔工具是设计工具很重要的一个功能。钢笔工具通过创建一组贝塞尔曲线,使得用户可以编辑创建任意不规则的曲线形状。钢笔工具使用三次贝塞尔曲线,每段贝塞尔曲线包含两个锚点定义起点和终点,两个控制点分别定义起始切线和收尾切线,通过特定算法形成曲线线段,基本可以拟合任意形状。

钢笔工具的实现效果:

请添加图片描述

钢笔组件的创建
整体逻辑
  1. 使用points数组变量记录路径(一组贝塞尔曲线)数值,point类型可以为锚点或控制点
  2. 监听mousedown事件:
    2.1. 第一次按下鼠标时再points数组中添加一个锚点和对应控制点
    2.2. 设置当前编辑锚点为最后一个锚点
  3. 监听mousemove事件:
    3.1. 对于非第一组锚点控制点的后续节点,在每次鼠标松开后第一次移动鼠标时,添加一组锚点和控制点
    3.2. 当鼠标没有按下,也就是仅移动鼠标时,改变当前锚点为鼠标移动位置
    3.3. 当鼠标按下时,也就是拖拽状态中,改变当前控制点位置为鼠标拖拽位置
    3.4. 当鼠标移动至第一个锚点附件,自动吸附,并设置闭合路径标志为true
  4. 监听mouseup事件:
    4.1. 重置相关变量
    4.2. 当闭合路径标志为true时创建组件
  5. 监听points等相关变量变化,相关变量改变时,重新绘制控制组件
临时变量

临时变量用于记录钢笔创建过程中的数据,组件创建完即重置。

  1. points
    使用points变量记录路径中的点,其中type可以设置为anchorcontrol,对于控制点,origin为其对应的锚点uuid
// 点的数据结构
// point data struct
export interface IPoint {uuid: string;x: number;y: number;type: string;origin: string | null;isShow?: boolean;
}export interface IPoints {value: Array<IPoint>;
}// 钢笔路径包含的点
// points for pen path
const points: IPoints = reactive({value: []
})
const setPoints = (value: Array<IPoint>) => {points.value = value
}
  1. editing
    editing标识是否正在编辑钢笔路径
// 是否正在编辑钢笔路径
// whether on editing
const editing: Ref<boolean> = ref(false)
const setEditing = (status: boolean) => {editing.value = status
}
  1. mousedown
    mousedonw变量记录当前鼠标是否按下
  2. mousemove
    mousemove变量记录当前鼠标是否移动
  3. editAnchor
    editAnchor变量记录当前编辑的锚点
  4. closePath
    closePath变量记录路径是否闭合,由于字体中不含有开放路径,只有路径闭合时,才能创建钢笔组件
钢笔组件数据结构

每个组件最外层数据结构如下:

// 字符组件数据结构,包含变换等基础信息,与包含图形信息的IComponentValue不同
// component data struct, contains transform info, etc, different with IComponentValue
export interface IComponent {uuid: string;type: string;name: string;lock: boolean;visible: boolean;value: IComponentValue;x: number;y: number;w: number;h: number;rotation: number;flipX: boolean;flipY: boolean;usedInCharacter: boolean;opacity?: number;
}

对于每个不同的组件,记录相应数据在IComponent的value字段中,IComponentValue枚举定义如下:

// 字符图形组件信息枚举
// enum of basic element info for component
export enum IComponentValue {IPenComponent,IPolygonComponent,IRectangleComponent,IEllipseComponent,IPictureComponent,ICustomGlyph,
}

对于钢笔组件,IPenComponent数据格式如下:

// 钢笔组件
// pen component
export interface IPenComponent {points: any;strokeColor: string;fillColor: string;closePath: boolean;editMode: boolean;contour?: Array<ILine | IQuadraticBezierCurve | ICubicBezierCurve>;preview?: Array<ILine | IQuadraticBezierCurve | ICubicBezierCurve>;
}

生成钢笔代码:

// 生成钢笔组件
// generate pen component
const genPenComponent = (points: Array<IPoint>,closePath: boolean,fillColor: string = '',strokeColor: string = '#000',
) => {const { x, y, w, h } = getBound(points.reduce((arr: Array<{x: number, y: number }>, point: IPoint) => {arr.push({x: point.x,y: point.y,})return arr}, []))const rotation = 0const flipX = falseconst flipY = falselet options = {unitsPerEm: 1000,descender: -200,advanceWidth: 1000,}if (editStatus.value === Status.Edit) {options.unitsPerEm = selectedFile.value.fontSettings.unitsPerEmoptions.descender = selectedFile.value.fontSettings.descenderoptions.advanceWidth = selectedFile.value.fontSettings.unitsPerEm}let transformed_points = transformPoints(points, {x, y, w, h, rotation, flipX, flipY,})const contour_points = formatPoints(transformed_points, options, 1)const contour = genPenContour(contour_points)const scale = 100 / (options.unitsPerEm as number)const preview_points = transformed_points.map((point) => {return Object.assign({}, point, {x: point.x * scale,y: point.y * scale,})})const preview_contour = genPenContour(preview_points)return {uuid: genUUID(),type: 'pen',name: 'pen',lock: false,visible: true,value: {points: points,fillColor,strokeColor,closePath,editMode: false,preview: preview_contour,contour: contour,} as unknown as IComponentValue,x,y,w,h,rotation: 0,flipX: false,flipY: false,usedInCharacter: true,}
}
钢笔工具初始化

设置相关变量:

mousedown.value = false
mousemove.value = false
eventListenersMap = {}
let editAnchor: any = null
const controlScale = 0.35
const nearD = 5
let closePath = false
let _lastControl: IPoint
let _controlIndex: number

设置事件监听器:

canvas.addEventListener('mousedown', onMouseDown)
canvas.addEventListener('mousemove', onMouseMove)
window.addEventListener('mouseup', onMouseUp)
window.addEventListener('keydown', onKeyDown)

设置关闭工具回调函数,该函数在关闭钢笔工具时被执行:

const closePen = () => {canvas.removeEventListener('mousedown', onMouseDown)canvas.removeEventListener('mousemove', onMouseMove)window.removeEventListener('mouseup', onMouseUp)window.removeEventListener('keydown', onKeyDown)setEditing(false)setPoints([])_lastControl = undefined_controlIndex = undefinedclosePath = falseeditAnchor = null
}
mousedown事件

第一次按下鼠标时再points数组中添加一个锚点和对应控制点:

if (!points.value.length) {// 第一个锚点const _anchor: IPoint = {uuid: genUUID(),type: 'anchor',x: getCoord(e.offsetX),y: getCoord(e.offsetY),origin: null,isShow: true,}const _control: IPoint = {uuid: genUUID(),type: 'control',x: getCoord(e.offsetX),y: getCoord(e.offsetY),origin: _anchor.uuid,isShow: true,}editAnchor = {uuid: _anchor.uuid,index: 0,}setPoints([_anchor, _control])editAnchor = {uuid: points.value[0].uuid,index: 0,}
}

非第一次按下鼠标,设置editAnchor值:

else {editAnchor = {uuid: points.value[points.value.length - 2].uuid,index: points.value.length - 2,}
}

监听器完整代码:

const onMouseDown = (e: MouseEvent) => {if (!points.value.length) {// 保存状态saveState('新建钢笔组件锚点', [StoreType.Pen,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo)}mousedown.value = truesetEditing(true)if (!points.value.length) {// 第一个锚点const _anchor: IPoint = {uuid: genUUID(),type: 'anchor',x: getCoord(e.offsetX),y: getCoord(e.offsetY),origin: null,isShow: true,}const _control: IPoint = {uuid: genUUID(),type: 'control',x: getCoord(e.offsetX),y: getCoord(e.offsetY),origin: _anchor.uuid,isShow: true,}editAnchor = {uuid: _anchor.uuid,index: 0,}setPoints([_anchor, _control])editAnchor = {uuid: points.value[0].uuid,index: 0,}} else {editAnchor = {uuid: points.value[points.value.length - 2].uuid,index: points.value.length - 2,}}
}
mousemove事件

对于非第一组锚点控制点的后续节点,在每次鼠标松开后第一次移动鼠标时,添加一组锚点和控制点:

if (!mousedown.value) {if (!mousemove.value && _points.length) {// 第一次移动鼠标// 保存状态saveState('新建钢笔组件锚点', [StoreType.Pen,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo)_lastControl = Object.assign({}, _points[_points.length - 1])_controlIndex = _points.length - 1const _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.value = true}
}

当鼠标没有按下,也就是仅移动鼠标时,改变当前锚点为鼠标移动位置:

else if (_points.length) {// 移动鼠标_controlIndex = _points.length - 4const _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)_control2.x = getCoord(e.offsetX)_control2.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.value = true
}

当鼠标按下时,也就是拖拽状态中,改变当前控制点位置为鼠标拖拽位置:

if (mousedown.value) {if (_lastControl) _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.value = truesetPoints(_points)
}

当鼠标移动至第一个锚点附件,自动吸附,并设置闭合路径标志为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) => {if (!points.value.length || !editing) returnconst _points = R.clone(points.value)if (mousedown.value) {if (_lastControl) _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.value = truesetPoints(_points)}if (!mousedown.value) {if (!mousemove.value && _points.length) {// 第一次移动鼠标// 保存状态saveState('新建钢笔组件锚点', [StoreType.Pen,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo)_lastControl = Object.assign({}, _points[_points.length - 1])_controlIndex = _points.length - 1const _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.value = true} else if (_points.length) {// 移动鼠标_controlIndex = _points.length - 4const _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)_control2.x = getCoord(e.offsetX)_control2.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.value = true}}
}
mouseup事件

mouseup事件中重置相关变量,如果closePath为true则创建组件

const onMouseUp = (e: MouseEvent) => {if (!points.value.length || !editing) returnmousedown.value = falsemousemove.value = falseeditAnchor = nullif (closePath) {setEditing(false)const component = genPenComponent(R.clone(points).value, true)setPoints([])if (!glyph) {addComponentForCurrentCharacterFile(component)} else {addComponentForCurrentGlyph(component)}_lastControl = undefined_controlIndex = undefinedclosePath = falseeditAnchor = null}
}
绘制钢笔组件控件

在钢笔组件创建过程中,编辑控件需要全程渲染辅助用户进行操作。程序需要监听points等相关变量变化,相关变量改变时,重新绘制控制组件。
监听事件:

watch([penPoints,penEditing,
], () => {render()tool.value === 'select' && renderSelectEditor(canvas.value)tool.value === 'pen' && renderPenEditor(canvas.value)if (!penEditing.value) returnrenderPenEditor(canvas.value)
})

控件绘制函数

const renderPenEditor = (canvas: HTMLCanvasElement) => {const ctx: CanvasRenderingContext2D = (canvas as HTMLCanvasElement).getContext('2d') as CanvasRenderingContext2Dconst _points = points.value.map((point: IPoint) => {return {isShow: point.isShow,...mapCanvasCoords({x: point.x,y: point.y,}),}})if (!_points.length) returnconst w = 10ctx.strokeStyle = '#000'ctx.fillStyle = '#000'ctx.beginPath()ctx.moveTo(_points[0].x, _points[0].y)if (_points.length >= 4) {ctx.bezierCurveTo(_points[1].x, _points[1].y, _points[2].x, _points[2].y, _points[3].x, _points[3].y)}for (let i = 3; i < _points.length - 1; i += 3) {if (i + 3 >= _points.length) breakctx.bezierCurveTo(_points[i + 1].x, _points[i + 1].y, _points[i + 2].x, _points[i + 2].y, _points[i + 3].x, _points[i + 3].y)}ctx.stroke()ctx.closePath()for (let i = 0; i < _points.length - 1; i += 3) {_points[i].isShow && ctx.fillRect(_points[i].x - w / 2, _points[i].y - w / 2, w, w)_points[i + 1].isShow && ctx.strokeRect(_points[i + 1].x - w / 2, _points[i + 1].y - w / 2, w, w)if ((i + 2) > _points.length - 1) break_points[i + 2].isShow && ctx.strokeRect(_points[i + 2].x - w / 2, _points[i + 2].y - w / 2, w, w)}ctx.beginPath()ctx.moveTo(_points[0].x, _points[0].y)ctx.lineTo(_points[1].x, _points[1].y)ctx.stroke()ctx.closePath()for (let i = 3; i < _points.length - 1; i += 3) {if (_points[i - 1].isShow) {ctx.beginPath()ctx.moveTo(_points[i].x, _points[i].y)ctx.lineTo(_points[i - 1].x, _points[i - 1].y)ctx.stroke()ctx.closePath()}if (_points[i + 1].isShow) {ctx.beginPath()ctx.moveTo(_points[i].x, _points[i].y)ctx.lineTo(_points[i + 1].x, _points[i + 1].y)ctx.stroke()ctx.closePath()}}
}
钢笔组件的编辑

钢笔组件创建后,用户可以随时编辑,所以编辑钢笔组件也是一个很重要的功能。

整体逻辑
  1. 通过临时变量selectPenPoint, hoverPenPoint等控制当前选择或鼠标划过的点
  2. 监听mousedown事件,如果鼠标距离某个点距离达到一定阈值,则记录该点为选中点selectPenPoint
  3. 监听mousemove事件,如果鼠标按下,则移动选中点,如果鼠标未按下,则检查是否划过某点,记录该点为hoverPenPoint
  4. 监听mouseup事件,清除临时变量
  5. 使用renderSelectPenEditor渲染控件,每次数值变化时,重新渲染
临时变量

临时变量用于记录钢笔编辑过程中的数据,组件编辑完即重置。

  1. selectAnchor
    当前选择的锚点
  2. selectPenPoint
    当前选择的点,可能为锚点或控制点
  3. hoverPenPoint
    当前鼠标移动至的点
编辑钢笔初始化

当用户切换到编辑模式下,首先调用initPenEditMode方法,该方法初始化事件监听器,和一些变量,并定义关闭编辑模式的方法回调。

// 选择钢笔组件时,初始化方法
// initializier for pen component selection
const initPenEditMode = (canvas: HTMLCanvasElement, d: number = 5, glyph: boolean = false) => {let lastX = -1let lastY = -1let mousedown = falseconst onMouseDown = (e: MouseEvent) => {//...}const onMouseMove = (e: MouseEvent) => {//...}const onMouseUp = (e: MouseEvent) => {//...}//...canvas?.addEventListener('mousedown', onMouseDown)document.addEventListener('mousemove', onMouseMove)document.addEventListener('mouseup', onMouseUp)canvas.addEventListener('keydown', onKeyDown)const closeSelect = () => {canvas?.removeEventListener('mousedown', onMouseDown)document.removeEventListener('mousemove', onMouseMove)document.removeEventListener('mouseup', onMouseUp)canvas.removeEventListener('keydown', onKeyDown)selectAnchor.value = ''selectPenPoint.value = ''hoverPenPoint.value = ''}return closeSelect
}
事件监听器

监听mousedown事件,如果鼠标距离某个点距离达到一定阈值,则记录该点为选中点selectPenPoint

const onMouseDown = (e: MouseEvent) => {mousedown = truefor (let i = orderedListWithItemsForCurrentCharacterFile.value.length - 1; i >= 0; i--) {const component = orderedListWithItemsForCurrentCharacterFile.value[i]const { x: _x, y: _y } = rotatePoint({ x: getCoord(e.offsetX), y: getCoord(e.offsetY) },{ x: component.x + component.w / 2, y: component.y + component.h / 2 },-component.rotation)if (selectedComponentUUID.value === component.uuid && component.visible) {const _points = transformPenPoints(selectedComponent.value, false)for (let i = 0; i < _points.length - 1; i++) {const point = _points[i]// 鼠标移动至pointif (distance(_x, _y, point.x, point.y) <= d) {if (point.type === 'anchor') {// 选择锚点selectAnchor.value = point.uuidselectPenPoint.value = point.uuidreturn} else if (selectAnchor.value) {// 选择控制点const _index: number = (() => {for (let j = 0; j < _points.length; j++) {if (_points[j].uuid === selectAnchor.value) {return j}}return -1})()if (i <= _index + 4 && i >= _index - 4) {selectPenPoint.value = point.uuid} else if (i === 1 && _index === _points.length - 1) {// 最后一个锚点(和第一个锚点重合),第二个控制点为第一个锚点的第一个控制点selectPenPoint.value = point.uuid}}}}return}}if (!selectedComponent.value.visible) returnconst { x, y, w, h, rotation, uuid} = selectedComponent.valueconst { x: _x, y: _y } = rotatePoint({ x: getCoord(e.offsetX), y: getCoord(e.offsetY) },{ x: x + w / 2, y: y + h / 2 },-rotation)lastX = _xlastY = _ysetSelectionForCurrentCharacterFile('')
}

监听mousemove事件,如果鼠标按下,则移动选中点,如果鼠标未按下,则检查是否划过某点,记录该点为hoverPenPoint

const onMouseMove = (e: MouseEvent) => {if (!selectedComponent.value || !selectedComponent.value?.visible) returnconst { x, y, w, h, rotation, uuid} = selectedComponent.valueconst { x: _x, y: _y } = rotatePoint({ x: getCoord(e.offsetX), y: getCoord(e.offsetY) },{ x: x + w / 2, y: y + h / 2 },-rotation)const penComponentValue = selectedComponent.value.value as unknown as IPenComponentconst { points, closePath } = penComponentValueif (mousedown) {const _points = R.clone(points)_points.map((point: IPoint, index: number) => {if (selectPenPoint.value === point.uuid) {// 对于闭合路径,起始锚点和收尾锚点重合,应该一致移动if (point.type === 'anchor' && closePath && ( index < 2 || index > _points.length - 3 )) {if (index < 2) {for(let i = _points.length - 2; i < _points.length; i++) {if (_points[i].type === 'anchor' && _points[i].x === point.x && _points[i].y === point.y) {_points[i].x = _x_points[i].y = _y}}} else if (index > _points.length - 3) {for(let i = 0; i < 2; i++) {if (points[i].type === 'anchor' && _points[i].x === point.x && _points[i].y === point.y) {_points[i].x = _x_points[i].y = _y}}}}// 对于闭合路径,起始控制点和收尾控制点重合,应该一致移动// TODOpoint.x = _xpoint.y = _y}return point})modifyComponentForCurrentCharacterFile(uuid, {value: {points: _points}})}if (!mousedown) {points.map((point: IPoint, index) => {if (distance(_x, _y, point.x, point.y) <= d) {if (point.type === 'control' && index === points.length - 1 && points.length >= 2 && point.x === points[1].x && point.y === points[1].y) {// 如果未闭合路径,且最后一个控制点和第一个控制点重合,改变第一个控制点return} else {hoverPenPoint.value = point.uuid}}})}lastX = _xlastY = _y
}

监听mouseup事件,清除临时变量

const onMouseUp = (e: MouseEvent) => {const comp = glyph ? selectedComponent_glyph.value : selectedComponent.valueif (!comp || !comp.visible) returnmodifyComponentValue()mousedown = falseselectControl.value = 'null'
}
使用renderSelectPenEditor渲染控件,每次数值变化时,重新渲染
// 渲染钢笔组件选择编辑器
// render selection editor for selected pen component
const renderSelectPenEditor = (canvas: HTMLCanvasElement, d: number = 10, glyph: boolean = false) => {if (!glyph && !selectedComponentUUID.value) returnif (glyph && !selectedComponentUUID_glyph.value) returnconst comp = glyph ? selectedComponent_glyph.value : selectedComponent.valueif (!comp.visible) returnconst { x, y, w, h, rotation, flipX, flipY, value: penComponentValue } = compconst _x = mapCanvasX(x)const _y = mapCanvasY(y)const _w = mapCanvasWidth(w)const _h = mapCanvasHeight(h)const ctx: CanvasRenderingContext2D = canvas?.getContext('2d') as CanvasRenderingContext2Dconst _points = transformPenPoints(glyph ? selectedComponent_glyph.value : selectedComponent.value, true)const _map = listToMap(_points, 'uuid')// index为selectAnchor对应的索引数值const { index, pointType } = (() => {for (let i = 0; i < _points.length; i++) {if (_points[i].uuid === selectAnchor.value) {return {index: i,pointType: _points[i].type}}}return { index: -1, pointType: '' }})()//ctx.strokeStyle = '#79bbff'ctx.strokeStyle = '#153063'ctx.translate(_x + _w / 2, _y + _h / 2)ctx.rotate(rotation * Math.PI / 180)ctx.translate(-(_x + _w / 2), -(_y + _h / 2))if (!selectAnchor.value) {for (let i = 0; i < _points.length; i++) {if (_points[i].type === 'anchor') {ctx.beginPath()ctx.ellipse(_points[i].x, _points[i].y, d, d, 0, 0, 2 * Math.PI);ctx.stroke()ctx.closePath()}}} else {for (let i = 0; i < _points.length - 1; i++) {if (_points[i].type === 'anchor') {ctx.beginPath()ctx.ellipse(_points[i].x, _points[i].y, d, d, 0, 0, 2 * Math.PI);ctx.stroke()ctx.closePath()}if (_points[i].type === 'control' && i >= index - 4 && i <= index + 4) {const originUUID = _points[i].origin as string// @ts-ignoreconst originAnchor: IPoint = _map[originUUID]ctx.strokeRect(_points[i].x - d, _points[i].y - d, 2 * d, 2 * d)ctx.beginPath()ctx.moveTo(_points[i].x, _points[i].y)ctx.lineTo(originAnchor.x, originAnchor.y)ctx.stroke()ctx.closePath()}if (index === _points.length - 1 && _points[i].type === 'control' && i === 1) {// 最后一个锚点(和第一个锚点重合),第二个控制点为第一个锚点的第一个控制点const originUUID = _points[i].origin as string// @ts-ignoreconst originAnchor: IPoint = _map[originUUID]ctx.strokeRect(_points[i].x - d, _points[i].y - d, 2 * d, 2 * d)ctx.beginPath()ctx.moveTo(_points[i].x, _points[i].y)ctx.lineTo(originAnchor.x, originAnchor.y)ctx.stroke()ctx.closePath()}}}for (let i = 0; i < _points.length; i++) {if (hoverPenPoint.value === _points[i].uuid) {ctx.fillStyle = '#79bbff'if (_points[i].type === 'anchor') {ctx.beginPath()ctx.ellipse(_points[i].x, _points[i].y, d, d, 0, 0, 2 * Math.PI);ctx.fill()ctx.closePath()}// 只显示选中锚点前后控制点if (_points[i].type === 'control' && i >= index - 4 && i <= index + 4) {	ctx.fillRect(_points[i].x - d, _points[i].y - d, 2 * d, 2 * d)}ctx.fillStyle = '#fff'}}ctx.setTransform(1, 0, 0, 1, 0, 0)
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com