手机端网站关键字排名,怎么看网站开发的好坏,企业网站建设规划设计任务书,吉林市网站创意与建设目录 字玩FontPlayer开发笔记14 Vue3实现多边形工具笔记整体流程临时变量多边形组件数据结构初始化多边形工具mousedown事件mousemove事件监听mouseup事件渲染控件将多边形转换为平滑的钢笔路径 字玩FontPlayer开发笔记14 Vue3实现多边形工具
字玩FontPlayer是笔者开源的一款字… 目录 字玩FontPlayer开发笔记14 Vue3实现多边形工具笔记整体流程临时变量多边形组件数据结构初始化多边形工具mousedown事件mousemove事件监听mouseup事件渲染控件将多边形转换为平滑的钢笔路径 字玩FontPlayer开发笔记14 Vue3实现多边形工具
字玩FontPlayer是笔者开源的一款字体设计工具使用Vue3 ElementUI开发源代码github | gitee
笔记
多变形工具允许用户创建自定义多边形形状实现效果
整体流程
使用points临时变量记录创建时多边形的顶点监听mousedown事件第一次点击时在points数组中添加首个顶点监听mousemove事件每次鼠标按下后第一次移动时在points数组中添加顶点非第一次移动则改变points中最后一个顶点的位置使其移动到鼠标当前位置监听mouseup事件如果路径闭合则创建多边形组件并重置临时变量使用renderPolygonEditor渲染控件每次变量更新时重新渲染控件字玩支持将多边形路径转换为平滑的钢笔路径使用paper.js实现
临时变量 points 记录创建时多边形的顶点数组 editing 记录当前是否在创建顶点过程中 mousedown 记录当前鼠标是否按下 mousemove 记录当前鼠标是否移动
多边形组件数据结构
每个组件最外层数据结构如下
// 字符组件数据结构包含变换等基础信息与包含图形信息的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,
}对于多边形组件IPolygonComponent数据格式如下
// 多边形组件
// polygon component
export interface IPolygonComponent {points: any;strokeColor: string;fillColor: string;closePath: boolean;contour?: ArrayILine | IQuadraticBezierCurve | ICubicBezierCurve;preview?: ArrayILine | IQuadraticBezierCurve | ICubicBezierCurve;
}生成多边形组件代码
// 生成多边形组件
// generate polygon component
const genPolygonComponent (points: ArrayIPoint, closePath: boolean) {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 genPolygonContour(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 genPolygonContour(preview_points)return {uuid: genUUID(),type: polygon,name: polygon,lock: false,visible: true,value: {points: points,fillColor: ,strokeColor: #000,closePath,preview: preview_contour,contour: contour,} as unknown as IComponentValue,x,y,w,h,rotation: 0,flipX: false,flipY: false,usedInCharacter: true,}
}初始化多边形工具
每次切换至多边形工具时首先进行工具的初始化包括添加事件监听器并定义关闭工具回调方法等。
// 多边形工具初始化方法
// initializer for polygon tool
const initPolygon (canvas: HTMLCanvasElement, glyph: boolean false) {mousedown.value falsemousemove.value falseconst nearD 5let closePath falseconst onMouseDown (e: MouseEvent) {//...}const onMouseMove (e: MouseEvent) {//...}const onMouseUp (e: MouseEvent) {//...}const onEnter (e: KeyboardEvent) {//...}const onKeyDown (e: KeyboardEvent) {//...}canvas.addEventListener(mousedown, onMouseDown)window.addEventListener(mousemove, onMouseMove)window.addEventListener(mouseup, onMouseUp)window.addEventListener(keydown, onKeyDown)const closePolygon () {canvas.removeEventListener(mousedown, onMouseDown)window.removeEventListener(mouseup, onMouseUp)window.removeEventListener(keydown, onKeyDown)window.removeEventListener(mousemove, onMouseMove)setEditing(false)setPoints([])closePath false}return closePolygon
}mousedown事件
监听mousedown事件第一次点击时在points数组中添加首个顶点
const onMouseDown (e: MouseEvent) {if (!points.value.length) {// 保存状态saveState(创建多边形组件, [StoreType.Polygon,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo)}setEditing(true)mousedown.value trueif (!points.value.length) {const _point: IPoint {uuid: genUUID(),x: getCoord(e.offsetX),y: getCoord(e.offsetY),}const _points R.clone(points.value)_points.push(_point)setPoints(_points)}
}mousemove事件
监听mousemove事件每次鼠标按下后第一次移动时在points数组中添加顶点非第一次移动则改变points中最后一个顶点的位置使其移动到鼠标当前位置
const onMouseMove (e: MouseEvent) {if (!points.value.length || !editing) returnconst _points R.clone(points.value)if (!mousedown.value) {if (!mousemove.value) {// 保存状态saveState(创建多边形组件, [StoreType.Polygon,glyph ? StoreType.EditGlyph : StoreType.EditCharacter],OpType.Undo)// 第一次移动鼠标const _point {uuid: genUUID(),x: getCoord(e.offsetX),y: getCoord(e.offsetY),}_points.push(_point)setPoints(_points)mousemove.value true} else {// 移动鼠标const _point _points[_points.length - 1]_point.x getCoord(e.offsetX)_point.y getCoord(e.offsetY)closePath falseif (isNearPoint(getCoord(e.offsetX), getCoord(e.offsetY), points.value[0].x, points.value[0].y, nearD)) {_point.x points.value[0].x_point.y points.value[0].yclosePath true}setPoints(_points)mousemove.value true}}
}监听mouseup事件
监听mouseup事件如果路径闭合则创建多边形组件并重置临时变量
const onMouseUp (e: MouseEvent) {if (!points.value.length || !editing) returnmousedown.value falsemousemove.value falseif (closePath) {setEditing(false)if (!glyph) {addComponentForCurrentCharacterFile(genPolygonComponent(R.clone(points.value), true))} else {addComponentForCurrentGlyph(genPolygonComponent(R.clone(points.value), true))}setPoints([])closePath false}
}渲染控件
// 渲染多边形编辑工具
// render polygon editor
const renderPolygonEditor (points: IPoints, canvas: HTMLCanvasElement) {const ctx: CanvasRenderingContext2D (canvas as HTMLCanvasElement).getContext(2d) as CanvasRenderingContext2Dconst _points points.value.map((point: IPoint) {return mapCanvasCoords({x: point.x,y: point.y,})})if (!_points.length) returnconst w 10ctx.strokeStyle #000ctx.fillStyle #000ctx.beginPath()ctx.moveTo(_points[0].x, _points[0].y)for (let i 1; i _points.length; i ) {ctx.lineTo(_points[i].x, _points[i].y)}ctx.stroke()ctx.closePath()for (let i 0; i _points.length; i) {ctx.fillRect(_points[i].x - w / 2, _points[i].y - w / 2, w, w)}
}将多边形转换为平滑的钢笔路径
实现效果
const transformToPath () {savePolygonEditState()const polygonComponent selectedComponent.value.valueconst { x, y, w, h, rotation, flipX, flipY } selectedComponent.valueconst points: Array{x: number,y: number,} transformPoints(polygonComponent.points.map((point: IPoint) {return {x: point.x,y: point.y,}}), {x, y, w, h, rotation, flipX, flipY,})let penPoints: ArrayIPenPoint []// 创建一个闭合多边形const segments []for(let i 0; i points.length - 1; i) {segments.push([points[i].x, points[i].y])}// 如果收尾节点和起始节点重合则不添加if (points[points.length - 1].x ! points[0].x || points[points.length - 1].y ! points[0].y) {segments.push([points[points.length - 1].x, points[points.length - 1].y])}let path new paper.Path({segments,closed: true,})path.smooth()let uuid1 genUUID()for (let i 0; i path.curves.length; i) {const curve path.curves[i]const uuid2 genUUID()const uuid3 genUUID()penPoints.push({uuid: uuid1,x: curve.points[0].x,y: curve.points[0].y,type: anchor,origin: null,isShow: true,})penPoints.push({uuid: uuid2,x: curve.points[1].x,y: curve.points[1].y,type: control,origin: uuid1,isShow: false,})uuid1 genUUID()penPoints.push({uuid: uuid3,x: curve.points[2].x,y: curve.points[2].y,type: control,origin: uuid1,isShow: false,})if (i path.curves.length - 1) {penPoints.push({uuid: uuid1,x: curve.points[3].x,y: curve.points[3].y,type: anchor,origin: null,isShow: true,})}}const { x: penX, y: penY, w: penW, h: penH } getBound(penPoints)if (editStatus.value Status.Edit) {modifyComponentForCurrentCharacterFile(selectedComponentUUID.value, {value: {points: penPoints,editMode: false,},type: pen,x: penX,y: penY,w: penW,h: penH,rotation: 0,})} else if (editStatus.value Status.Glyph) {modifyComponentForCurrentGlyph(selectedComponentUUID_Glyph.value, {value: {points: penPoints,editMode: false,},type: pen,x: penX,y: penY,w: penW,h: penH,rotation: 0,})}
}