ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JointJS 画布自由绘制实战:矩形、椭圆、直线与自由曲线绘图工具的实现指南

JointJS 画布自由绘制实战:矩形、椭圆、直线与自由曲线绘图工具的实现指南 前端UI组件【免费下载链接】jointA proven SVG-based JavaScript diagramming library powering exceptional UIs项目地址https://gitcode.com/gh_mirrors/jo/joint点击查看免费下载本指南基于开源仓库 jointJointJS中的 shapes-drawing-js 示例完整讲解如何为基于 JointJS 的图形编辑器增加自由绘制能力让用户直接在画布上拖拽画出矩形、椭圆、直线乃至任意自由曲线。读完本文你将掌握该示例的安装运行方式、五种绘图工具的状态机设计、拖拽实时预览与正式落图的完整实现并理解joint/core中VSVG 矢量器、g几何库与Paper事件体系在这一场景中的协同用法。示例概览一个可运行的绘图工具演示examples/shapes-drawing-js是 monorepo 中的一个 Vite 驱动的纯前端演示核心文件如下examples/shapes-drawing-js/index.html页面骨架包含画布容器与顶部工具栏单选按钮examples/shapes-drawing-js/src/main.js全部绘图逻辑约 220 行examples/shapes-drawing-js/src/styles.scss工具栏、预览形状与光标反馈样式examples/shapes-drawing-js/package.json依赖joint/core工作区版本与vite提供dev/build/preview三个脚本。其核心思路是在 JointJS 的Paper上监听blank:pointerdown/element:pointerdown等指针事件拖拽过程中先用V()向 SVG 视口追加一个预览形状实时跟随鼠标松手后把预览几何数据换算为最终的 Cell如standard.Rectangle、standard.Ellipse、standard.Path、standard.Polyline通过graph.addCell()加入图模型从而完成一次绘制。快速上手安装、开发与构建示例的 README 给出了标准的 monorepo 工作流。首先从仓库根目录安装全部依赖并构建工作区包yarn install yarn run build仓库根 package.json 中workspaces将./packages/*与./examples/*一并纳入管理packageManager指定为yarn4.18.0yarn run build会通过yarn workspaces foreach依次构建所有工作区包括joint/core与各示例确保示例依赖的joint/core已就绪。随后进入示例目录启动开发服务器yarn dev示例的 package.json 中dev: vite启动后终端会打印本地地址通常为http://localhost:5173。打开页面即可在工具栏切换工具、在画布上拖拽绘制。如需生成生产构建yarn build该脚本为tsc vite build输出生成在dist/目录。若想本地预览生产产物运行yarn preview同样会启动一个静态服务器供体验dist/中的成品。界面入口工具栏与画布容器index.html 定义了极简的界面骨架一个占满页面的#paper-container容器以及一组typeradio的工具按钮div idtools input typeradio nametools idPointer label forPointerPointer/label input typeradio nametools idLine label forLineLine/label input typeradio nametools idRectangle checked label forRectangleRectangle/label input typeradio nametools idEllipse label forEllipseEllipse/label input typeradio nametools idFreeDraw label forFreeDrawFree Draw/label /div默认选中Rectangle。main.js末尾通过setTool(document.querySelector([checked])?.id ?? Pointer)读取初始选中项并给#tools挂上change事件监听实现运行时切换工具。工具栏的视觉样式选中态高亮、圆角分段、焦点轮廓等在 styles.scss 中以 SCSS 变量与 mixin 实现。图纸与纸张的初始化main.js 首先创建图模型与纸张const graph new dia.Graph({}, { cellNamespace: shapes }); const paper new dia.Paper({ model: graph, cellViewNamespace: shapes, width: 100%, height: 100%, async: true, sorting: dia.Paper.sorting.APPROX, background: { color: #F3F7F6 } }); paperContainer.appendChild(paper.el);几个值得注意的配置cellNamespace: shapes/cellViewNamespace: shapes把joint/core内置的standard.*形状命名空间注入图与视图使后续graph.addCell({ type: standard.Rectangle })能正确解析出对应的 Cell 与 Viewasync: true启用异步渲染画布元素较多时拖拽预览与绘制仍能保持流畅sorting: dia.Paper.sorting.APPROX使用近似排序策略渲染视图层级属于性能优化选项background: { color: #F3F7F6 }设置画布底色与后面预览形状的绿色描边形成视觉对比。工具状态机五种模式绘图行为由一组枚举驱动main.jsconst Tool { Pointer: 0, Line: 1, Rectangle: 2, Ellipse: 3, FreeDraw: 4 }; let tool Tool.Pointer;Pointer模式是普通的选择/交互模式其余四种是绘图模式。setTool()main.js负责在切换时同步 Paper 的交互能力function setTool(toolId) { tool Tool[toolId]; paper.setInteractivity(tool Tool.Pointer); paper.el.classList.toggle(paper-active-tools, tool ! Tool.Pointer); }Paper.prototype.setInteractivity见 packages/joint-core/src/dia/Paper.mjs#L4105-L4110会把值写入this.options.interactive并通过invoke(this._views, setInteractivity, value)广播给所有视图各 CellView 的setInteractivity则仅更新自己的options.interactive见 packages/joint-core/src/dia/CellView.mjs#L1379-L1382。因此进入绘图模式时画布上既有元素会失去拖拽/交互能力避免与绘制手势冲突同时给paper.el加上paper-active-tools类由 SCSS 统一将光标设为十字准星crosshair给用户明确的正在画图反馈。拖拽中的实时预览dragStart 与 drag所有绘图工具共用同一套按下 → 移动 → 松开流程。dragStartmain.js在指针按下时记录起点坐标并用V()joint/core的 SVG 矢量器直接向paper.viewport追加一个临时预览元素case Tool.Line: { evt.data.x1 x; evt.data.y1 y; evt.data.x2 x; evt.data.y2 y; data.vel V(line, { x1: x, y1: y, x2: x, y2: y }); break; } case Tool.Rectangle: { data.vel V(rect, { x, y, width: 1, height: 1 }); break; } case Tool.Ellipse: { data.vel V(ellipse, { cx: x, cy: y, rx: 1, ry: 1 }); break; } case Tool.FreeDraw: { data.vel V(polyline); evt.data.points [[x, y]]; break; }data.vel.appendTo(paper.viewport)把预览元素放入画布 SVG 视口addClass(preview-shape)套用预览样式。Pointer模式直接返回不做任何预览。dragmain.js在指针移动时用几何库g计算起点到当前点的包围盒const bbox new g.Rect(ox, oy, x - ox, y - oy); if (bbox.width 0) bbox.width 1; if (bbox.height 0) bbox.height 1; bbox.normalize();normalize()保证向左/向上拖拽时宽度、高度为正1的兜底值避免出现 0 尺寸的退化几何。随后按工具类型更新预览元素属性直线更新x2/y2矩形直接套用bbox.toJSON()椭圆换算rx/ry/cx/cy自由绘制则把每个采样点压入points数组并序列化为polyline的points字符串。落笔成图dragEnd 中四种形状的正式生成松手后dragEndmain.js先vel.remove()移除预览元素再依据bbox生成正式 Cell。这里体现了 JointJS 常见的最佳实践预览阶段只用轻量 SVG 元素最终结果才进入图模型。直线旋转对齐的 standard.Pathconst line new g.Line({ x: x1, y: y1 }, { x: x2, y: y2 }); const angle line.angle(); const { start } line.clone().rotate(line.midpoint(), angle); graph.addCell({ type: standard.Path, angle, position: { x: start.x, y: start.y }, size: { width: line.length(), height: 1 }, attrs: { body: { d: M 0 calc(0.5 * h) H calc(w) } } });其技巧在于standard.Path的size为水平走向的width: line.length()通过g.Line计算倾斜角angle并让 Cell 整体旋转对齐路径数据M 0 calc(0.5 * h) H calc(w)利用calc()表达式画一条从左上角垂直中点0.5 * h到右上角的水平线旋转后即得到斜线效果。calc()是 JointJS 路径属性中基于元素宽高计算的表达式无需手算具体坐标。矩形与椭圆标准形状的直接落图case Tool.Rectangle: graph.addCell({ type: standard.Rectangle, position: { x, y }, size: { width, height } }); break; case Tool.Ellipse: graph.addCell({ type: standard.Ellipse, position: { x, y }, size: { width, height } }); break;这里position/size直接取自bbox而预览阶段椭圆需手动换算cx/cy/rx/ry——正式 Cell 则交给standard.Ellipse以 position size 语义托管体现了预览纯 SVG 几何与模型Cell 语义两种表达方式的差异。自由绘制折线的简化与序列化const geometry new g.Polyline(points.join( )); geometry.simplify({ threshold: 0.8 }); const geometryBBox geometry.bbox(); graph.addCell({ type: standard.Polyline, position: { x: geometryBBox.x, y: geometryBBox.y }, size: { width: geometryBBox.width, height: geometryBBox.height }, attrs: { body: { refPoints: geometry.serialize() } } });鼠标轨迹的原始采样点可能成百上千直接入图会带来多余的顶点。这里用g.Polyline.simplify({ threshold: 0.8 })做抽稀threshold为容差阈值数值越大顶点越少、曲线越平滑。simplify()在joint/core内部同样被用于路径计算——例如 packages/joint-core/src/dia/LinkView.mjs#L538 中连线求交后对折线做simplify({ threshold: SIMPLIFY_THRESHOLD })化简packages/joint-core/src/routers/rightAngle.mjs#L40 的直角路由也在收尾时以threshold: 1简化路径点可见这是库内通用的几何精简手段。落图时以简化后几何的包围盒作为 Cell 的position/size再用refPoints引用坐标系内的相对点集geometry.serialize()把g.Polyline序列化为x1,y1 x2,y2 ...的字符串。这样无论图形画在画布哪个位置其路径数据都相对自身包围盒定义便于后续移动、缩放与编辑。事件绑定与指针语义预览与落图的完整闭环由 main.js 的四组 Paper 事件驱动paper.on(blank:pointerdown, (evt, x, y) dragStart(evt, x, y)); paper.on(element:pointerdown, (_, evt, x, y) dragStart(evt, x, y)); paper.on(blank:pointermove, (evt, x, y) drag(evt, x, y)); paper.on(element:pointermove, (_, evt, x, y) drag(evt, x, y)); paper.on(blank:pointerup, (evt) dragEnd(evt)); paper.on(element:pointerup, (_, evt) dragEnd(evt));注意blank:*与element:*两套事件都绑定了相同的处理函数前者处理空白处起笔后者让用户在已有元素之上也能继续绘制新形状回调首参被忽略。Paper的指针事件统一携带(evt, x, y)形式的本地坐标处理函数直接消费即可无需自行做坐标系换算——这是把绘制逻辑做在Paper事件层而非原生 DOM 事件上的关键收益。拖拽的中间状态存放在evt.data这个共享对象上dragStart写入、drag更新、dragEnd读取结构清晰且天然与单个手势生命周期绑定。预览样式与光标反馈styles.scss 为绘制体验提供了视觉反馈.preview-shape { stroke: $green2; // #08b081 fill: none; stroke-width: 2; } .paper-active-tools * { cursor: crosshair; }预览形状使用绿色描边、无填充、2px 线宽与浅色画布背景#F3F7F6形成对比进入任何绘图工具时整张画布显示十字准星光标。二者配合setTool中的paper-active-tools类切换用户能随时感知当前处于绘制模式。工具栏按钮选中态$green2背景 绿色光晕0 0 10px rgba($green1, 0.5)与预览描边同色系保持了视觉语言的一致。扩展思路在理解本示例的实现骨架后可以自然地扩展出更多能力新增工具在Tool枚举中追加类型并在dragStart/drag/dragEnd三个 switch 中补齐对应分支例如多边形V(polygon)、圆角矩形V(rect, { rx, ry })、自由贝塞尔曲线等撤销/重做graph.addCell()是模型层的变更可结合joint/core的graph事件如add/remove/change自行记录操作栈实现绘图历史约束与吸附drag中可在更新bbox前对坐标取整或吸附到网格配合g.Rect的几何运算即可辅助键与修饰可读取evt上的修饰键如 Shift强制正方形/正圆只需在生成bbox后做宽高等比化。小结shapes-drawing-js演示了在 JointJS 中实现用户自由绘图的完整范式Paper指针事件提供统一坐标、V()提供轻量 SVG 预览、g几何库负责包围盒与折线化简、standard.*形状负责正式落图配合setInteractivity在绘图模式与选择模式间切换。这套代码量不大但信息密度很高的示例是任何想在 JointJS 之上构建可画图编辑器如拓扑图、示意图、批注工具的开发者的理想起点。赞分享前端UI组件【免费下载链接】jointA proven SVG-based JavaScript diagramming library powering exceptional UIs项目地址https://gitcode.com/gh_mirrors/jo/joint点击查看免费下载相关推荐CommandlineConfig性能优化让你的配置读写速度快10倍的秘诀CommandlineConfig性能优化让你的配置读写速度快10倍的秘诀 CommandlineConfig是一个供用户以Python Dict或JSON格BabelDOC路径绘制直线、曲线、矩形等图形BabelDOC路径绘制直线、曲线、矩形等图形 在文档处理过程中图形元素的精确渲染是保证排版质量的关键环节。BabelDOC作为一款功能强大的文档翻译工具人工智能AI 应用NLP计算机视觉Open-Pencil 图形绘制完全指南矩形、椭圆、线条、画框与区块的创建与属性调优Open Pencil 图形绘制完全指南矩形、椭圆、线条、画框与区块的创建与属性调优 本文围绕 Open PencilAI 原生设计编辑器底部的绘图工具栏前端桌面应用AI 应用MCP 服务上一篇Gatsby GraphQL 数据层实战以 graphql-reference 示例中的 Break with a Banshee 为样本读懂 Markdown 内容如何变成可查询节点下一篇RxJS includes 操作符完全指南在 Observable 流中检索元素与 fromIndex 起始索引解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表