恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
JointJS 缩放控制工具实战:基于 elementTools.Control 自定义元素缩放手柄
首页
资讯中心
/
JointJS 缩放控制工具实战:基于 elementTools.Control 自定义元素缩放手柄
JointJS 缩放控制工具实战:基于 elementTools.Control 自定义元素缩放手柄
发布时间:2026/10/6 12:12:53
前端UI组件【免费下载链接】jointA proven SVG-based JavaScript diagramming library powering exceptional UIs项目地址https://gitcode.com/gh_mirrors/jo/joint点击查看免费下载本文基于官方示例resize-control-tool-js示例 README展开讲解如何在 JointJS 中为元素挂载一个可拖拽的缩放手柄Resize Control Tool。文章既给出可直接复制的完整代码与运行命令也深入joint/core源码Control.mjs剖析 Control 工具的参数契约与鼠标交互事件流帮助读者理解并自定义属于自己的元素编辑工具。一、示例项目是什么resize-control-tool-js是 JointJS monorepo仓库根目录见 package.json中的一个纯 JavaScript 示例位于 examples/resize-control-tool-js。它的目标是回答一个高频需求如何让用户通过拖拽手柄方便地缩放画布中的元素示例的运行效果是画布中有一个 100×100 的标准矩形矩形的右下角带有一个缩放图标手柄按住手柄拖拽时矩形实时改变尺寸同时手柄周围出现一个虚线外框提示目标范围松开鼠标后虚线框消失缩放完成。examples/resize-control-tool-js/ ├── assets/ │ ├── icons/ │ │ └── resize.svg # 缩放手柄图标紫色箭头图标 │ └── jointjs-logo-black.svg ├── src/ │ ├── main.js # 全部示例逻辑 │ └── styles.css # 画布容器样式 ├── index.html # 页面骨架挂载 #paper-container ├── README.md # 安装 / 开发 / 构建 / 预览说明 └── package.json # 名为 joint/demo-resize-control-tool-js 的 Vite 工程工程通过package.json以joint/core: workspace:^的方式引用 monorepo 内的核心库使用 Vite 作为开发/构建工具源码为 ESM 模块。二、安装、开发与构建示例 README 提供了完整的四步工作流均需在仓库环境内执行1. 安装与构建在 monorepo 根目录执行yarn install yarn run build第一步安装整个仓库含joint/core工作区依赖第二步完成核心包的构建。2. 开发模式在示例目录内执行yarn dev启动 Vite 开发服务器然后在终端打印的地址访问页面通常为http://localhost:5173。3. 生产构建yarn build产物会生成在dist/目录由 package.json 中tsc vite build脚本驱动。4. 本地预览生产构建yarn preview该命令启动一个静态服务器预览dist/中的产物。三、核心实现逐行拆解示例的全部逻辑都在 src/main.js约 75 行分为三个部分创建画布、定义缩放工具、给元素挂载工具。3.1 创建 Graph 与 Paperimport { dia, shapes, elementTools } from joint/core; import ./styles.css; import resizeIcon from ../assets/icons/resize.svg; const graph new dia.Graph({}, { cellNamespace: shapes }); const paper new dia.Paper({ model: graph, cellViewNamespace: shapes, width: 100%, height: 100%, gridSize: 20, async: true, sorting: dia.Paper.sorting.APPROX, background: { color: #F3F7F6 } }); paperContainer.appendChild(paper.el); paper.setGrid(mesh);几个值得注意的配置项gridSize: 20设置 20 像素的逻辑网格后续拖拽缩放的坐标会被吸附到网格上clientToLocalPoint在 Paper.mjs 中结合snapToGrid实现。这样元素尺寸会以 20 为粒度变化避免毛边。async: true开启异步渲染适合交互频繁的场景。sorting: dia.Paper.sorting.APPROX使用近似排序算法Paper.mjs 附近可以看到drawGrid相关初始化在大图中性能更好。background: { color: #F3F7F6 }设置浅色背景。paper.setGrid(mesh)显示网格线让缩放结果在视觉上有参照。页面骨架由 index.html 提供样式见 styles.css核心就是让#paper-container铺满视口并支持滚动。3.2 定义 ResizeTool扩展 elementTools.Control这是示例的核心——一个自定义的缩放手柄工具const ResizeTool elementTools.Control.extend({ children: [ { tagName: image, selector: handle, attributes: { cursor: pointer, width: 20, height: 20, xlink:href: resizeIcon } }, { tagName: rect, selector: extras, attributes: { pointer-events: none, fill: none, stroke: #33334F, stroke-dasharray: 2,4, rx: 5, ry: 5 } } ], getPosition: function(view) { const model view.model; const { width, height } model.size(); return { x: width, y: height }; }, setPosition: function(view, coordinates) { const model view.model; model.resize( Math.max(coordinates.x - 10, 1), Math.max(coordinates.y - 10, 1) ); } });这段代码蕴含了 Control 工具的完整契约1children自定义工具的 SVG 标记markupControl默认自带两个子节点见 Control.mjs圆形handle和虚线矩形extras。示例用 20×20 的resize.svg图片图标文件替换了默认圆形手柄保留了相同语义的extras虚线框。2getPosition(view)告诉工具手柄该放在哪里返回手柄在元素相对坐标系中的位置。示例直接返回元素当前尺寸{ x: width, y: height }即元素右下角——手柄始终贴住右下角元素变大手柄跟着走。3setPosition(view, coordinates)拖拽时手柄坐标如何写回模型拖拽过程中框架会持续调用setPosition传入相对坐标示例将其换算为新的宽高并写入模型coordinates.x - 10手柄图标本身 20×20中心偏移 10减去后图标中心与指针对齐视觉上握住了手柄Math.max(..., 1)防止宽高被拖成 0 或负数。3.3 把工具挂到元素上const rectangle new shapes.standard.Rectangle(); rectangle.resize(100, 100); rectangle.position(100, 100); rectangle.addTo(graph); rectangle.findView(paper).addTools( new dia.ToolsView({ tools: [ new ResizeTool({ selector: body }) ] }) );创建标准矩形并加入图之后通过findView(paper)拿到它的视图调用addTools传入一个dia.ToolsView其中tools数组包含new ResizeTool({ selector: body })。这里的selector: body是 Control 的关键选项工具内部的虚线extras矩形需要知道参考哪个子元素来计算包围盒body正是standard.Rectangle中矩形主体的 selector。框架通过relatedView.findNode(selector)找到该节点并计算其 bbox见 Control.mjs于是虚线框始终与矩形主体轮廓一致。四、源码级原理Control 工具如何工作理解 cellTools/Control.mjs 有助于把这个示例推广到任意缩放/旋转/拖拽工具。4.1 完整选项表Control 继承自ToolView其options默认为选项默认值作用handleAttributesnull附加到handle节点上的额外 SVG 属性如cursor、pointer-events在updateHandle中逐个setAttribute写入selectorroot计算extras虚线框包围盒时参考的元素子节点示例传bodypadding6虚线框相对参考节点 bbox 的向外扩展量传NaN会被置为 0scalenull手柄自身的缩放比例getHandleTransformString会在translate后追加scale(...)适合应对 paper 缩放重要约束Control 的 markup 必须包含selector: handle的节点否则update()会抛出Control: markup selector handle is requiredControl.mjs。4.2 事件驱动的完整交互链路Control 绑定了两类事件Control.mjs自身事件mousedown/touchstart→onPointerDowndblclick/dbltap→onPointerDblClickdocument 级事件mousemove/touchmove→onPointerMovemouseup/touchend/touchcancel→onPointerUp。一次完整拖拽的时序如下onPointerDown先guard(evt)判断是否命中忽略区域stopPropagationpreventDefault阻断画布自身的拖拽调用paper.undelegateEvents()临时接管全局事件并delegateDocumentEvents()toggleExtras(true)显示虚线框model.startBatch(control-move)开启批处理让多次resize合并为一次事务例如只触发一次 change 事件onPointerMovedocument 事件把指针的客户端坐标依次转换为paper.clientToLocalPoint(clientX, clientY)的本地坐标、再通过model.getRelativePointFromAbsolute(coords)转换为元素相对坐标随后调用可覆盖的this.setPosition(relatedView, relativeCoords, evt)并this.update()重算手柄与虚线框位置。示例正是在这里把相对坐标换算成model.resize(width, height)onPointerUppaper.delegateEvents()归还事件、undelegateDocumentEvents()、blur()、toggleExtras(false)隐藏虚线框、stopBatch(control-move)结束批处理onPointerDblClick调用可覆盖的resetPosition(relatedView, evt)。示例未覆盖它基类为空实现因此双击无动作若你的工具支持双击复位在此实现即可。这套down 时接管 document 事件、up 时归还的机制保证了即使指针拖出画布或浏览器窗口拖拽也不会丢失。4.3 getPosition 与坐标系统getHandleTransformStringControl.mjs决定手柄的实际渲染位置const relativePos this.getPosition(relatedView, this); const translate this.isOverlay() // 工具渲染在 paper 坐标系中 ? model.getAbsolutePointFromRelative(relativePos) // 工具渲染在 relatedView 的坐标系中 : relativePos;也就是说Control 要求getPosition返回元素相对坐标是否转换由工具处于 overlay 模式还是随元素变换决定。示例的手柄随元素一起平移旋转所以直接使用相对坐标{ x: width, y: height }。4.4 ToolsView 如何承载工具ToolsView.mjs 是工具的容器configure(options)遍历tools数组为每个ToolView实例调用tool.configure(relatedView, this)并挂到g容器ToolsView.mjsupdate()按顺序updateVisibility()对可见工具首次render()后续update()更新位置与外观focusTool/blurTool实现聚焦一个工具、隐藏其他工具的常见交互。示例中rectangle.findView(paper).addTools(new dia.ToolsView({...}))即完成视图 ↔ 工具容器 ↔ 工具实例三者绑定之后工具随元素视图的更新请求TOOLSflag自动刷新。4.5 底层Element.resize拖拽最终落到 Element.mjs 的resize(width, height, opt)。它改变元素的size()结果并触发相关视图重绘——示例中getPosition恰好读取model.size()作为手柄位置因此 resize 与手柄形成闭环拖拽 → 写回尺寸 → 手柄位置更新 → 下一帧拖拽以新位置为基准。五、扩展思路与注意事项基于上面这套机制可以很容易派生更多工具多方向缩放将getPosition改为返回四个边角/边中点如{ x: 0, y: height }表示左下角并给每个方向各建一个Control实例放进ToolsView的tools数组同时显示辅助虚线框复用extrasselector机制把参考节点换成其他子元素如标签、图标即可实现包围盒预览双击复位覆盖resetPosition(view, evt)在onPointerDblClick时恢复元素初始尺寸配合 paper 缩放当调用paper.scale()后可通过scale选项让手柄大小不随画布缩放避免手柄过大或过小。实现时留意两点一是setPosition中务必对最小尺寸做防护示例用Math.max(x, 1)二是若手柄贴边布局记得像示例一样扣除手柄自身尺寸的一半作为偏移否则拖拽点与图标中心错位。六、小结resize-control-tool-js用约 75 行代码演示了 JointJS 元素缩放工具的完整范式elementTools.Control.extend()覆写childrenmarkup、getPosition手柄定位与setPosition坐标写回模型再通过dia.ToolsView挂载到元素视图上。结合 Control.mjs 源码可见其背后是完整的事件委托、坐标转换clientToLocalPoint→getRelativePointFromAbsolute、批处理startBatch/stopBatch与包围盒计算体系。读者可以把这个示例当作模板复制其结构、替换children中的图标与getPosition/setPosition逻辑即可在几分钟内实现自定义的拖拽、旋转或其他编辑工具想要深入了解可继续阅读核心库中的 dia/ToolsView.mjs、dia/ToolView.mjs 与 dia/Element.mjs。赞分享前端UI组件【免费下载链接】jointA proven SVG-based JavaScript diagramming library powering exceptional UIs项目地址https://gitcode.com/gh_mirrors/jo/joint点击查看免费下载相关推荐JointJS 工具缩放指南使用 Tools 的 scale 选项调整元素与连接线工具尺寸JointJS 工具缩放指南使用 Tools 的 scale 选项调整元素与连接线工具尺寸 JointJS 中元素Element与连接线Link的工具前端UI组件Mole 插件系统实战3 步写出第一个 Mac 自定义清理规则Mole 插件系统实战3 步写出第一个 Mac 自定义清理规则 Mole 是开源的 Mac 清理与优化工具它的插件系统允许你用一段 Bash 清理脚本登记自CLI开发工具运维观测上一篇企业级前端监控ant-design-vue-pro中的前端性能指标监控下一篇告别风扇噪音FanControl如何让你完全掌控Windows电脑散热系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考