LogicFlow 画布 API 完全指南:resize / focusOn / zoom / fitView 与坐标换算实战
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
本文以 LogicFlow 实例的画布相关 API 文档为主体,系统讲解画布尺寸调整、视口变换(缩放与平移)、坐标体系换算、元素层级与边动画共 16 个实例方法。结合
packages/core中LogicFlow.tsx、TransformModel.ts、GraphModel.ts与BaseEdgeModel.ts的源码实现,帮助读者掌握每个方法的签名、行为细节、底层原理,并给出可直接落地的组合使用方案。
LogicFlow 是一个专注于业务自定义的流程图编辑框架。在实际业务中,无论是"工具栏上的放大缩小按钮"、"导航栏里的回到中心/自适应画布",还是"右键菜单里的置顶元素",背后都由一组画布级实例方法驱动。本文要解决的正是这一层能力:如何精确控制画布尺寸、如何缩放与平移视口、如何在页面坐标与画布坐标之间换算、如何管理元素层级与边动画。读完本文,你将能熟练使用resize、focusOn、zoom、fitView、getPointByClient等 API,并理解它们各自在底层如何影响TransformModel的变换矩阵。
一、方法总览与调用入口
所有画布相关方法都定义在 LogicFlow 实例(lf)上,核心实现集中在 packages/core/src/LogicFlow.tsx 的 "Graph 相关方法" 区块(约 L939-L1295)。它们大多是一层薄封装,真正的状态与计算逻辑位于GraphModel与TransformModel中:
| 方法 | 作用 | 底层委托对象 |
|---|---|---|
resize(width?, height?) | 调整画布尺寸 | GraphModel.resize |
focusOn(focusOnArgs) | 视口中心定位到节点/坐标 | TransformModel.focusOn |
zoom(zoomSize?, point?) | 按步进或倍率缩放 | TransformModel.zoom |
resetZoom() | 缩放重置为 1 | TransformModel.resetZoom |
setZoomMiniSize(size) | 设置最小缩放倍数 | TransformModel |
setZoomMaxSize(size) | 设置最大缩放倍数 | TransformModel |
getTransform() | 获取缩放/平移状态 | TransformModel各 observable 字段 |
translate(x, y) | 相对偏移平移画布 | TransformModel.translate |
resetTranslate() | 平移复位 | 基于translate反向补偿 |
translateCenter() | 图形内容居中显示 | GraphModel.translateCenter |
fitView(v?, h?) | 图形自适应视口 | GraphModel.fitView |
getPointByClient(x, y) | 页面坐标转画布坐标 | GraphModel.getPointByClient |
toFront(id) | 元素置顶 | GraphModel.toFront |
openEdgeAnimation(edgeId) | 开启边动画 | BaseEdgeModel.openEdgeAnimation |
closeEdgeAnimation(edgeId) | 关闭边动画 | BaseEdgeModel.closeEdgeAnimation |
理解这张表之后,下文按"尺寸、视口定位、缩放、平移、自适应、坐标换算、层级、边动画"八个主题逐一深入。
二、画布尺寸:resize
签名
resize(width?: number, height?: number): voidresize用于调整画布尺寸;不传参数时自动按容器(container)当前的实际尺寸重算。源码中LogicFlow.resize将调用透传给graphModel.resize(width, height),并同步回写this.options.width / this.options.height(见 LogicFlow.tsx#L1024-L1028)。
从 GraphModel.ts#L1609-L1637 可以看到底层实现有三层保护性检查,这对实际调用非常有指导意义:
- 实例未销毁:
rootEl不存在时直接返回; - 元素仍在 DOM 中:通过
document.body.contains(this.rootEl)判断容器是否已被移除; - 元素可见:通过
this.rootEl.offsetParent !== null判断容器是否可见(例如处于display: none状态)。
通过检查后,宽高取值逻辑为:
this.width = width ?? this.rootEl.getBoundingClientRect().width this.height = height ?? this.rootEl.getBoundingClientRect().height即显式传入的宽高优先,否则读取getBoundingClientRect()的结果。若容器可见但计算出的宽高为 0,控制台会输出警告,提示确认 container 已挂载到 DOM。
另外值得注意:GraphModel内部使用ResizeObserver监听容器尺寸变化并自动调用resize()(见 GraphModel.ts#L207-L228),同时会发出graph:resize事件。因此绝大多数场景下你无需手动调用resize,只有容器尺寸发生非常规变化(例如动画过渡、拖拽改变面板宽度后)时才需要显式触发。
三、视口中心定位:focusOn
签名
focusOn(focusOnArgs: { id?: string; coordinate?: { x: number; y: number } }): voidfocusOn将视口中心移动到指定元素或坐标点。它实际上是重载方法,从 LogicFlow.tsx#L998-L1018 可以看到支持三种入参形态:
- 传入
string:视为元素 id,通过focusByElement找到节点中心(nodeModel.getData()的 x/y)或边的文本位置(edgeModel.textPosition); - 传入
{ x, y }:视为画布坐标,直接执行focusByCoordinate; - 传入
{ id?, coordinate? }:标准文档形态,两者二选一。
底层计算在 TransformModel.ts#L227-L234 的focusOn中完成,核心思路是"先求差、再平移":
const [x, y] = this.CanvasPointToHtmlPoint([targetX, targetY]) const [deltaX, deltaY] = [width / 2 - x, height / 2 - y] this.TRANSLATE_X += deltaX this.TRANSLATE_Y += deltaY即:先把目标画布坐标通过CanvasPointToHtmlPoint(乘缩放比例并加平移量,见 TransformModel.ts#L96-L102)换算成当前视口下的 HTML 坐标,再计算"视口中心点与该点"的差值,最后把差值叠加到平移量上,从而让目标点恰好落在视口中央。
一个常见误区:focusOn只改变平移(TRANSLATE),不会改变缩放(SCALE)。若想让目标点以特定倍率居中显示,需要先zoom再focusOn,下文"常见组合示例"会给出完整写法。
四、缩放控制:zoom / resetZoom / setZoomMiniSize / setZoomMaxSize
4.1 zoom:按步进或倍率缩放
签名
zoom(zoomSize?: boolean | number, point?: [number, number]): stringzoomSize为boolean时,按内置步进缩放:true放大、false缩小,步长为ZOOM_SIZE = 0.04(即每次变化 4%,见 TransformModel.ts#L57 与 TransformModel.ts#L152-L180);zoomSize为number时,直接设置目标倍率:小于 1 缩小、大于 1 放大、等于 1 不变;point为缩放原点(相对画布左上角的坐标),传入后缩放会围绕该点进行——实现上是同步修正平移量TRANSLATE_X -= (newScaleX - SCALE_X) * point[0],保证原点处的图形位置不因缩放而漂移;- 返回值:
string类型的当前缩放比例百分比,例如"100%"、"120%"。
缩放会受上下限约束:newScaleX小于MINI_SCALE_SIZE(默认 0.2)或大于MAX_SCALE_SIZE(默认 16)时,直接返回当前比例且不生效。
4.2 resetZoom:缩放复位
resetZoom(): void将SCALE_X、SCALE_Y重置为1(见 TransformModel.ts#L196-L201)。注意它只重置缩放,不影响平移量。
4.3 缩放边界:setZoomMiniSize / setZoomMaxSize
setZoomMiniSize(size: number): void setZoomMaxSize(size: number): void分别设置缩放允许的最小、最大倍率。默认值定义在 TransformModel.ts#L49-L50:MINI_SCALE_SIZE = 0.2(最小缩到 20%)、MAX_SCALE_SIZE = 16(最大放大到 1600%)。
典型用法:当业务流程不允许用户把图画得过小时,可在初始化后收紧最小倍率:
lf.setZoomMiniSize(0.5) // 最小缩到 50% lf.setZoomMaxSize(2) // 最大放到 200%4.4 变换事件监听
zoom、resetZoom、translate、focusOn每次执行后都会通过emitGraphTransform发出GRAPH_TRANSFORM(graph:transform)事件,携带type与完整的SCALE_X / SCALE_Y / SKEW_X / SKEW_Y / TRANSLATE_X / TRANSLATE_Y六元组(见 TransformModel.ts#L182-L194)。这为"监听画布缩放/平移,实时刷新缩略图或工具栏百分比"提供了官方事件通道,可配合lf.on('graph:transform', cb)使用。
五、平移控制:translate / resetTranslate / getTransform
5.1 translate:相对平移
translate(x: number, y: number): void按相对偏移量平移画布。底层 TransformModel.ts#L203-L218 在叠加前会校验平移边界:
if (this.TRANSLATE_X + x <= this.translateLimitMaxX && this.TRANSLATE_X + x >= this.translateLimitMinX) { this.TRANSLATE_X += x }平移上下限由updateTranslateLimits决定,来自初始化选项stopMoveGraph(见 TransformModel.ts#L41-L46):
false:四个方向均可无限平移[-Infinity, 0, Infinity, 0];vertical:禁止横向移动(仅纵向可移);horizontal:禁止纵向移动(仅横向可移);- 也支持传入
[minX, minY, maxX, maxY]四元组自定义限制区域。
也就是说,translate并非无脑累加,而是会被stopMoveGraph配置约束的。该配置可通过lf.updateEditConfig({ stopMoveGraph: 'vertical' })动态修改。
5.2 resetTranslate:平移复位
resetTranslate(): void将画布平移状态重置到初始位置。实现非常直观——读取当前平移量并反向平移(见 LogicFlow.tsx#L1252-L1256):
const { TRANSLATE_X, TRANSLATE_Y } = transformModel this.translate(-TRANSLATE_X, -TRANSLATE_Y)5.3 getTransform:读取变换状态
getTransform(): { SCALE_X: number; SCALE_Y: number; TRANSLATE_X: number; TRANSLATE_Y: number; }返回当前缩放与平移四元组(见 LogicFlow.tsx#L1227-L1237)。在内部,TransformModel还维护SKEW_X / SKEW_Y(当前恒为 0)以及ZOOM_SIZE,并据此生成渲染样式matrix(SCALE_X, SKEW_Y, SKEW_X, SCALE_Y, TRANSLATE_X, TRANSLATE_Y)(见 TransformModel.ts#L132-L144)。
典型场景:保存/恢复画布视角,或读取当前倍率显示在 UI 上:
const { SCALE_X } = lf.getTransform() toolbar.scaleText = `${Math.round(SCALE_X * 100)}%`六、自适应与居中:translateCenter / fitView
6.1 translateCenter:内容居中
translateCenter(): void将图内容(所有节点构成的"虚拟矩形")居中显示在视口内。GraphModel通过getVirtualRectSize计算所有节点的包围盒——遍历所有节点,取x ± width/2 ± strokeWidth、y ± height/2 ± strokeWidth的极值(见 GraphModel.ts#L1656-L1678),然后调用transformModel.focusOn把虚拟矩形中心平移到容器中心(见 GraphModel.ts#L1695-L1714)。注意:图内容为空(无节点)时方法直接返回,不产生任何效果。
6.2 fitView:内容自适应视口
fitView(verticalOffset?: number, horizontalOffset?: number): void自动调整缩放与平移,使全部节点完整显示在当前视口,并留出边距。参数含义:
verticalOffset:内容距视口上下的边距,默认20;horizontalOffset:内容距视口左右的边距,默认20。
源码中有一个向后兼容细节(LogicFlow.tsx#L1270-L1275):只传一个参数时,horizontalOffset会被赋值为verticalOffset,即fitView(20)等价于fitView(20, 20)。
底层算法在 GraphModel.ts#L1721-L1750,可概括为三步:
- 计算内容虚拟矩形尺寸与中心(
getVirtualRectSize); - 分别计算 X/Y 两个方向的缩放比,取较大者求倒数作为最终缩放倍率:
const zoomRatioX = (virtualRectWidth + horizontalOffset) / containerWidth const zoomRatioY = (virtualRectHeight + verticalOffset) / containerHeight const zoomRatio = 1 / Math.max(zoomRatioX, zoomRatioY)取
Math.max意味着以更"撑满"的那个维度为准,从而保证内容在两个方向上都不会溢出视口; - 以视口中心为原点执行
transformModel.zoom(zoomRatio, point),再通过transformModel.focusOn把虚拟矩形中心移动到视口中心。
fitView与translateCenter的区别在于:translateCenter只平移不缩放,fitView会同时调整缩放与平移。二者配合即可实现"先自适应、再精确微调"的工作流。
七、坐标换算:getPointByClient
签名
getPointByClient(x: number, y: number): { domOverlayPosition: { x: number; y: number }; canvasOverlayPosition: { x: number; y: number }; }将**页面坐标(client 坐标)**转换为画布坐标,同时返回 DOM 层与 SVG 层的两份结果。它同样支持对象入参getPointByClient({ x, y })(见 LogicFlow.tsx#L1050-L1064)。
底层实现在 GraphModel.ts#L401-L419:
const bbox = this.rootEl.getBoundingClientRect() const domOverlayPosition = { x: x1 - bbox.left, y: y1 - bbox.top } const [x, y] = this.transformModel.HtmlPointToCanvasPoint([ domOverlayPosition.x, domOverlayPosition.y, ]) const canvasOverlayPosition = { x, y }两个返回值的语义非常关键:
domOverlayPosition:页面坐标减去画布容器左上角位置,即以画布左上角为原点的坐标,未考虑缩放与平移。它对应 DOM 覆盖层(如 HTML 自定义节点所在的dom-overlay层)的坐标系;canvasOverlayPosition:在上一步基础上再经过HtmlPointToCanvasPoint((x - TRANSLATE_X) / SCALE_X,见 TransformModel.ts#L84-L90)去除缩放与平移影响,即真正的画布/业务坐标。它对应 SVG 覆盖层(canvas-overlay层)的坐标系,与节点数据中的x / y处于同一坐标系。
这是所有"鼠标点击 → 画布坐标"场景的官方入口。例如在画布任意位置右键添加节点时,需要用canvasOverlayPosition作为新节点的x / y,否则节点位置在画布缩放/平移后会"跑偏"。
八、元素层级:toFront
签名
toFront(id: string): void将指定节点或边置于更高层级(置顶)。id可以是节点 id 或边 id,底层在GraphModel.toFront中同时从nodesMap与edgesMap查找元素(见 GraphModel.ts#L806-L824)。
具体行为与画布的**堆叠模式(overlapMode)**强相关,这一点在 LogicFlow.tsx#L1030-L1039 的注释中有明确说明:
- 静态模式(STATIC):
toFront不做任何处理,直接返回; - 递增模式(INCREASE):将目标元素 zIndex 设置为"当前最大 zIndex + 1";
- 默认模式(节点在上 / 边在上):先把原置顶元素
topElement恢复原有层级(setZIndex()),再将目标元素设为最大 zIndex(ELEMENT_MAX_Z_INDEX)并记录为新的topElement。
这意味着默认模式下连续对两个元素调用toFront,前一个置顶元素会自动"退位",始终保持只有一个元素处于最顶层;而递增模式下所有被置顶的元素会按调用顺序层层叠高。
九、边动画:openEdgeAnimation / closeEdgeAnimation
openEdgeAnimation(edgeId: string): void closeEdgeAnimation(edgeId: string): void按边 id 开启或关闭边的流动动画。两个方法在LogicFlow层直接委托给graphModel.openEdgeAnimation / closeEdgeAnimation(见 LogicFlow.tsx#L1281-L1289),GraphModel再定位到对应边模型调用(见 GraphModel.ts#L1756-L1768)。
真正改变状态的是 BaseEdgeModel.ts#L686-L694:
openEdgeAnimation(): void { this.isAnimation = true } closeEdgeAnimation(): void { this.isAnimation = false }即通过响应式字段isAnimation控制边是否渲染动画。常用场景包括:
- 流程执行中高亮"正在流转"的连线;
- 数据同步/消息推送链路的状态可视化;
- 配合
lf.openEdgeAnimation(edgeId)与延时后lf.closeEdgeAnimation(edgeId)实现脉冲效果。
需要说明的是,动画的实际呈现(如虚线流动、流光描边)依赖边视图对isAnimation的渲染支持;对于业务自定义边,需要自行在视图层消费该状态字段。
十、常见组合示例与实战建议
文档给出了两组经典组合,这里结合源码进一步补充注释与说明:
// 场景一:先自适应,再居中,再按指定倍率缩放 // resize() 不带参数会按容器实际尺寸重算画布宽高(含 DOM 可见性检查) lf.resize(); // translateCenter 将所有节点的虚拟矩形中心平移到视口中心(不改变缩放) lf.translateCenter(); // zoom(1.2) 直接设置倍率为 120%,围绕视口中心缩放 lf.zoom(1.2); // 场景二:点坐标转换后定位到该处 // getPointByClient 把页面坐标转换为画布坐标(DOM 层 + SVG 层) const point = lf.getPointByClient(300, 200); // canvasOverlayPosition 与节点 x/y 同一坐标系,可直接用于定位 lf.focusOn({ coordinate: point.canvasOverlayPosition });其他高频实战组合:
// 初始化完成后立即自适应视口 lf.fitView(30, 30); // 点击节点时聚焦到该节点 lf.focusOn({ id: 'node_1' }); // 工具栏"缩小/放大/复位" lf.zoom(false); // 缩小一档(×0.96 左右,受最小值约束) lf.zoom(true); // 放大一档(×1.04 左右,受最大值约束) lf.resetZoom(); // 直接回到 100% // 鼠标右键添加节点:用 canvasOverlayPosition 作为节点坐标 const handleCanvasClick = (e: MouseEvent) => { const { canvasOverlayPosition } = lf.getPointByClient(e.clientX, e.clientY) lf.addNode({ type: 'rect', x: canvasOverlayPosition.x, y: canvasOverlayPosition.y }) } // 流程执行时点亮一条边,3 秒后熄灭 lf.openEdgeAnimation(edgeId) setTimeout(() => lf.closeEdgeAnimation(edgeId), 3000) // 订阅变换事件,实时同步工具栏缩放百分比 lf.on('graph:transform', ({ transform }) => { console.log('当前缩放比例:', `${transform.SCALE_X * 100}%`) })关键注意事项
- 缩放与平移独立:
focusOn/translateCenter只动平移,zoom/resetZoom只动缩放,fitView两者都动。需要同时控制时请显式组合调用; - 缩放边界全局生效:
setZoomMiniSize/setZoomMaxSize会影响包括zoom、fitView在内的一切缩放入口,因为fitView内部也是走transformModel.zoom,同样会被边界拦截; - 坐标换算选对坐标系:涉及"画布业务坐标"一律取
canvasOverlayPosition;仅需"相对容器的 DOM 位置"时取domOverlayPosition; - 空画布保护:
translateCenter、fitView在无节点时直接返回;resize在容器不可见/未挂载时会跳过重算,必要时请确认容器已挂载; - 层级行为随 overlapMode 变化:默认模式与递增模式(
OverlapMode.INCREASE)下toFront语义不同,静态模式(OverlapMode.STATIC)下置顶无效,请结合业务选择合适的堆叠模式。
结语
画布 API 是 LogicFlow 实例层最常用的能力集合之一。本文从官方文档的 16 个方法出发,逐一落到LogicFlow.tsx → GraphModel → TransformModel / BaseEdgeModel的调用链上,解释了缩放边界、平移限制、坐标双层的换算原理以及fitView的取最大值算法等源码细节。掌握这些方法及其底层行为,足以支撑工具栏、缩略图、导航定位、右键菜单等绝大多数业务定制场景。如需进一步了解事件机制与编辑配置,可继续阅读 LogicFlow 实例 API 文档 中的其他章节。
【免费下载链接】LogicFlowA flow chart editing framework focus on business customization. 专注于业务自定义的流程图编辑框架,支持实现脑图、ER图、UML、工作流等各种图编辑场景。项目地址: https://gitcode.com/GitHub_Trending/lo/LogicFlow
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考