Skip to content

Commit 027c0df

Browse files
z-pangolinDymoneLewis
authored andcommitted
fix: 修复移动端框选、缩放之间的冲突以及锚点连接时的问题,支持双指滚动
1 parent 84d0d49 commit 027c0df

File tree

10 files changed

+135
-98
lines changed

10 files changed

+135
-98
lines changed

packages/core/src/model/EditConfigModel.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export interface IEditConfigType {
125125
edgeTextMode: TextMode
126126
// 开启网格对齐
127127
snapGrid: boolean
128+
isPinching: boolean
128129
}
129130

130131
export type IConfigKeys = keyof IEditConfigType
@@ -186,6 +187,7 @@ const allKeys = [
186187
'edgeTextMultiple', // 是否支持多个边文本
187188
'nodeTextVertical', // 节点文本是否纵向显示
188189
'edgeTextVertical', // 边文本是否纵向显示
190+
'isPinching', //是否是双指捏合态
189191
] as const
190192

191193
/**
@@ -202,6 +204,7 @@ export class EditConfigModel {
202204
@observable stopMoveGraph = false
203205
@observable stopScrollGraph = false
204206
@observable snapGrid = false
207+
@observable isPinching = false
205208
/*********************************************************
206209
* 文本相关配置(全局)
207210
********************************************************/

packages/core/src/model/GraphModel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,7 @@ export class GraphModel {
12481248
clearSelectElements() {
12491249
this.selectElements.forEach((element) => {
12501250
element?.setSelected(false)
1251+
element?.setHovered(false)
12511252
})
12521253
this.selectElements.clear()
12531254
/**

packages/core/src/util/drag.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,6 @@ export class StepDrag {
104104
if (e.button !== LEFT_MOUSE_BUTTON_CODE) return
105105
if (this.isStopPropagation) e.stopPropagation()
106106
e.preventDefault()
107-
const target = e.target as any
108-
// 使用 Pointer Capture 保证后续 pointermove/pointerup 事件派发到当前目标
109-
// 在移动端或复杂 DOM 场景下,可避免因隐式捕获导致的事件丢失
110-
if (target && typeof target.setPointerCapture === 'function') {
111-
target.setPointerCapture(e.pointerId)
112-
}
113107
this.isStartDragging = true
114108
this.startX = e.clientX
115109
this.startY = e.clientY

packages/core/src/view/Anchor.tsx

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface IProps {
2929
anchorIndex: number
3030
graphModel: GraphModel
3131
nodeModel: BaseNodeModel
32-
setHoverOff: (e: MouseEvent) => void
32+
setHoverOff: (e: PointerEvent) => void
3333
}
3434

3535
interface IState {
@@ -154,7 +154,7 @@ class Anchor extends Component<IProps, IState> {
154154
endY: y1,
155155
dragging: true,
156156
})
157-
this.moveAnchorEnd(x1, y1)
157+
this.moveAnchorEnd(x1, y1, event)
158158
if (nearBoundary.length > 0 && !stopMoveGraph && autoExpand) {
159159
this.t = createRaf(() => {
160160
const [translateX, translateY] = nearBoundary
@@ -164,7 +164,7 @@ class Anchor extends Component<IProps, IState> {
164164
endX: endX - translateX,
165165
endY: endY - translateY,
166166
})
167-
this.moveAnchorEnd(endX - translateX, endY - translateY)
167+
this.moveAnchorEnd(endX - translateX, endY - translateY, event)
168168
})
169169
}
170170
eventCenter.emit(EventType.ANCHOR_DRAG, {
@@ -189,6 +189,11 @@ class Anchor extends Component<IProps, IState> {
189189
this.sourceRuleResults.clear()
190190
this.targetRuleResults.clear()
191191
const { graphModel, nodeModel, anchorData } = this.props
192+
// 拖拽结束清理:取消悬浮态
193+
if (this.preTargetNode) {
194+
this.preTargetNode.setHovered(false)
195+
this.preTargetNode = undefined
196+
}
192197

193198
graphModel.eventCenter.emit(EventType.ANCHOR_DRAGEND, {
194199
data: anchorData,
@@ -289,7 +294,7 @@ class Anchor extends Component<IProps, IState> {
289294
}
290295
}
291296

292-
moveAnchorEnd(endX: number, endY: number) {
297+
moveAnchorEnd(endX: number, endY: number, event?: PointerEvent) {
293298
const { graphModel, nodeModel, anchorData } = this.props
294299
const info = targetNodeInfo(
295300
{
@@ -344,12 +349,33 @@ class Anchor extends Component<IProps, IState> {
344349
} else {
345350
targetNode.setElementState(ElementState.NOT_ALLOW_CONNECT)
346351
}
352+
// 人工触发进入目标节点事件,同步设置 hovered 以驱动锚点显隐和样式
353+
if (!targetNode.isHovered) {
354+
const nodeData = targetNode.getData()
355+
if (event) {
356+
graphModel.eventCenter.emit(EventType.NODE_MOUSEENTER, {
357+
data: nodeData,
358+
e: event,
359+
})
360+
}
361+
targetNode.setHovered(true)
362+
}
347363
} else if (
348364
this.preTargetNode &&
349365
this.preTargetNode.state !== ElementState.DEFAULT
350366
) {
351367
// 为了保证鼠标离开的时候,将上一个节点状态重置为正常状态。
352368
this.preTargetNode.setElementState(ElementState.DEFAULT)
369+
// 未命中任何节点:人工派发离开事件并取消悬浮,避免状态残留
370+
const prevData = this.preTargetNode.getData()
371+
if (event) {
372+
graphModel.eventCenter.emit(EventType.NODE_MOUSELEAVE, {
373+
data: prevData,
374+
e: event,
375+
})
376+
}
377+
this.preTargetNode.setHovered(false)
378+
this.preTargetNode = undefined
353379
}
354380
}
355381

packages/core/src/view/behavior/dnd.ts

Lines changed: 44 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -38,72 +38,64 @@ export class Dnd {
3838
}
3939
}
4040

41+
isInsideCanvas(e: PointerEvent): boolean {
42+
const overlay = this.lf.graphModel.rootEl.querySelector(
43+
'[name="canvas-overlay"]',
44+
) as HTMLElement | null
45+
const topEl = window.document.elementFromPoint(
46+
e.clientX,
47+
e.clientY,
48+
) as HTMLElement | null
49+
return (
50+
topEl === overlay ||
51+
(topEl !== null && !!overlay && overlay.contains(topEl))
52+
)
53+
}
4154
startDrag(nodeConfig: OnDragNodeConfig) {
4255
const { editConfigModel } = this.lf.graphModel
43-
if (!editConfigModel?.isSilentMode) {
44-
this.nodeConfig = nodeConfig
45-
// 指针移动:根据命中结果判断是否在画布覆盖层上,驱动假节点创建/移动或清理
46-
this.docPointerMove = (e: PointerEvent) => {
47-
if (!this.nodeConfig) return
48-
// 获取画布覆盖层元素(仅在其自身或后代命中时视为“在画布内”)
49-
const overlay = this.lf.graphModel.rootEl.querySelector(
50-
'[name="canvas-overlay"]',
51-
) as HTMLElement | null
52-
// 获取当前指针位置下最上层的DOM元素,判断当前指针是否“在画布上”
53-
const topEl = window.document.elementFromPoint(
54-
e.clientX,
55-
e.clientY,
56-
) as HTMLElement | null
57-
const inside = topEl === overlay || (topEl && overlay?.contains(topEl))
58-
// 离开画布:清理吸附线与假节点
59-
if (!inside) {
60-
this.onDragLeave()
61-
return
62-
}
63-
// 首次进入画布:创建假节点并初始化位置
64-
if (!this.fakeNode) {
65-
this.dragEnter(e)
66-
return
67-
}
68-
// 在画布内移动:更新假节点位置与吸附线
69-
this.onDragOver(e)
56+
if (editConfigModel?.isSilentMode) return
57+
this.nodeConfig = nodeConfig
58+
// 指针移动:根据命中结果判断是否在画布覆盖层上,驱动假节点创建/移动或清理
59+
this.docPointerMove = (e: PointerEvent) => {
60+
if (!this.nodeConfig) return
61+
// 离开画布:清理吸附线与假节点
62+
if (!this.isInsideCanvas(e)) {
63+
this.onDragLeave()
64+
return
7065
}
71-
// 指针抬起:在画布内落点生成节点,否则清理假节点
72-
this.docPointerUp = (e: PointerEvent) => {
73-
if (!this.nodeConfig) return
74-
const overlay = this.lf.graphModel.rootEl.querySelector(
75-
'[name="canvas-overlay"]',
76-
) as HTMLElement | null
77-
const topEl = window.document.elementFromPoint(
78-
e.clientX,
79-
e.clientY,
80-
) as HTMLElement | null
81-
const inside = topEl === overlay || (topEl && overlay?.contains(topEl))
82-
if (inside) {
83-
this.onDrop(e)
84-
} else {
85-
this.onDragLeave()
86-
}
87-
// 阻止默认行为与冒泡,避免滚动/点击穿透
88-
e.preventDefault()
89-
e.stopPropagation()
90-
// 结束拖拽并移除监听
91-
this.stopDrag()
66+
// 首次进入画布:创建假节点并初始化位置
67+
if (!this.fakeNode) {
68+
this.dragEnter(e)
69+
return
9270
}
93-
window.document.addEventListener('pointermove', this.docPointerMove)
94-
window.document.addEventListener('pointerup', this.docPointerUp)
71+
// 在画布内移动:更新假节点位置与吸附线
72+
this.onDragOver(e)
9573
}
74+
// 指针抬起:在画布内落点生成节点,否则清理假节点
75+
this.docPointerUp = (e: PointerEvent) => {
76+
if (!this.nodeConfig) return
77+
if (this.isInsideCanvas(e)) {
78+
this.onDrop(e)
79+
} else {
80+
this.onDragLeave()
81+
}
82+
// 阻止默认行为与冒泡,避免滚动/点击穿透
83+
e.preventDefault()
84+
e.stopPropagation()
85+
// 结束拖拽并移除监听
86+
this.stopDrag()
87+
}
88+
window.document.addEventListener('pointermove', this.docPointerMove)
89+
window.document.addEventListener('pointerup', this.docPointerUp)
9690
}
9791

9892
stopDrag = () => {
9993
this.nodeConfig = null
10094
if (this.docPointerMove) {
10195
window.document.removeEventListener('pointermove', this.docPointerMove)
102-
this.docPointerMove = undefined
10396
}
10497
if (this.docPointerUp) {
10598
window.document.removeEventListener('pointerup', this.docPointerUp)
106-
this.docPointerUp = undefined
10799
}
108100
}
109101
dragEnter = (e: PointerEvent) => {
@@ -165,17 +157,6 @@ export class Dnd {
165157
this.lf.graphModel.removeFakeNode()
166158
this.fakeNode = null
167159
}
168-
169-
// eventMap() {
170-
// return {
171-
// // onPointerEnter: this.dragEnter,
172-
// // onPointerOver: this.dragEnter, // IE11
173-
// // onMouseMove: this.onDragOver,
174-
// // onPointerLeave: this.onDragLeave,
175-
// // onMouseOut: this.onDragLeave, // IE11
176-
// // onMouseUp: this.onDrop,
177-
// }
178-
// }
179160
}
180161

181162
export default Dnd

packages/core/src/view/edge/BaseEdge.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ export abstract class BaseEdge<P extends IProps> extends Component<
444444
/**
445445
* 不支持重写,如果想要基于contextmenu事件做处理,请监听edge:contextmenu事件。
446446
*/
447-
handleContextMenu = (ev: MouseEvent | PointerEvent) => {
447+
handleContextMenu = (ev: MouseEvent) => {
448448
ev.preventDefault()
449449
// 节点右击也会触发时间,区分右击和点击(mouseup)
450450
this.contextMenuTime = new Date().getTime()

packages/core/src/view/node/BaseNode.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export abstract class BaseNode<P extends IProps = IProps> extends Component<
249249
gridSize,
250250
} = graphModel
251251
model.isDragging = true
252-
const { clientX, clientY } = event as PointerEvent
252+
const { clientX, clientY } = event!
253253
let {
254254
canvasOverlayPosition: { x, y },
255255
} = graphModel.getPointByClient({
@@ -337,7 +337,7 @@ export abstract class BaseNode<P extends IProps = IProps> extends Component<
337337
}
338338
}
339339

340-
handleClick = (e: MouseEvent | PointerEvent) => {
340+
handleClick = (e: MouseEvent) => {
341341
// 节点拖拽进画布之后,不触发click事件相关emit
342342
// 点拖拽进画布没有触发mousedown事件,没有startTime,用这个值做区分
343343
const isDragging = this.mouseUpDrag === false
@@ -406,7 +406,7 @@ export abstract class BaseNode<P extends IProps = IProps> extends Component<
406406
}
407407
}
408408

409-
handleContextMenu = (ev: MouseEvent | PointerEvent) => {
409+
handleContextMenu = (ev: MouseEvent) => {
410410
ev.preventDefault()
411411
const { model, graphModel } = this.props
412412
const { editConfigModel } = graphModel
@@ -550,6 +550,7 @@ export abstract class BaseNode<P extends IProps = IProps> extends Component<
550550
onPointerDown={this.handleMouseDown}
551551
onPointerUp={this.handleMouseUp}
552552
onClick={this.handleClick}
553+
//因为移动端点击操作完成会按顺序触发enter、leave、click事件,所以会造成节点的闪烁,所以在这里没有统一状态为Pointer
553554
onMouseEnter={this.setHoverOn}
554555
onMouseOver={this.setHoverOn}
555556
onMouseLeave={this.setHoverOff}

0 commit comments

Comments
 (0)