fix: 连线位置偏移修复 - 改用 offsetLeft/offsetTop 替代 getBoundingClientRect
根因: getBoundingClientRect 返回屏幕坐标(受 scale 影响), 初始定位过程中 scale 从 1→0.45 变化时,连线在中间状态计算, 导致坐标与最终布局不匹配。 修复: 使用 offsetLeft/offsetTop 遍历 offsetParent 链获取本地坐标, 完全不受 CSS transform/scale 影响,消除 scale 依赖。
This commit is contained in:
@@ -150,7 +150,7 @@ export function TreeLayout({
|
||||
return checkHidden(nodeId)
|
||||
}, [getMember, collapsedNodes])
|
||||
|
||||
// 计算所有连线位置(使用 requestAnimationFrame 批量计算,避免 layout thrashing)
|
||||
// 计算所有连线位置(使用 offsetLeft/offsetTop 获取本地坐标,不受 scale 影响)
|
||||
const rafIdRef = useRef<number | null>(null)
|
||||
const calculateConnections = useCallback(() => {
|
||||
if (rafIdRef.current !== null) {
|
||||
@@ -158,9 +158,22 @@ export function TreeLayout({
|
||||
}
|
||||
rafIdRef.current = requestAnimationFrame(() => {
|
||||
rafIdRef.current = null
|
||||
const container = containerRef.current
|
||||
if (!container || nodeRefs.current.size === 0) return
|
||||
|
||||
// 辅助函数:获取元素相对于容器的本地坐标(不受 scale/transform 影响)
|
||||
const getLocalRect = (el: HTMLElement) => {
|
||||
let top = 0, left = 0
|
||||
let node = el
|
||||
while (node && node !== container) {
|
||||
top += node.offsetTop
|
||||
left += node.offsetLeft
|
||||
node = node.offsetParent as HTMLElement
|
||||
}
|
||||
return { top, left, width: el.offsetWidth, height: el.offsetHeight }
|
||||
}
|
||||
|
||||
const newConnections: Connection[] = []
|
||||
const containerRect = containerRef.current?.getBoundingClientRect()
|
||||
if (!containerRect || nodeRefs.current.size === 0) return
|
||||
|
||||
nodeRefs.current.forEach((nodeElement, nodeId) => {
|
||||
const member = getMember(nodeId)
|
||||
@@ -168,22 +181,22 @@ export function TreeLayout({
|
||||
if (collapsedNodes.has(nodeId)) return
|
||||
if (isNodeHiddenByCollapse(nodeId)) return
|
||||
if (!document.body.contains(nodeElement)) return
|
||||
const parentRect = nodeElement.getBoundingClientRect()
|
||||
const parentRect = getLocalRect(nodeElement)
|
||||
if (parentRect.width === 0 || parentRect.height === 0) return
|
||||
|
||||
const parentCenterX = (parentRect.left + parentRect.width / 2 - containerRect.left) / scale
|
||||
const parentBottomY = (parentRect.bottom - containerRect.top) / scale
|
||||
const parentCenterX = parentRect.left + parentRect.width / 2
|
||||
const parentBottomY = parentRect.top + parentRect.height
|
||||
|
||||
const childPositions = member.childrenIds
|
||||
.map(childId => {
|
||||
const childElement = nodeRefs.current.get(childId)
|
||||
if (!childElement || !document.body.contains(childElement)) return null
|
||||
const childRect = childElement.getBoundingClientRect()
|
||||
const childRect = getLocalRect(childElement)
|
||||
if (childRect.width === 0 || childRect.height === 0) return null
|
||||
return {
|
||||
id: childId,
|
||||
centerX: (childRect.left + childRect.width / 2 - containerRect.left) / scale,
|
||||
topY: (childRect.top - containerRect.top) / scale
|
||||
centerX: childRect.left + childRect.width / 2,
|
||||
topY: childRect.top
|
||||
}
|
||||
})
|
||||
.filter(Boolean) as Array<{ id: string; centerX: number; topY: number }>
|
||||
@@ -218,9 +231,10 @@ export function TreeLayout({
|
||||
|
||||
setConnections(newConnections)
|
||||
})
|
||||
}, [getMember, collapsedNodes, isNodeHiddenByCollapse, scale])
|
||||
}, [getMember, collapsedNodes, isNodeHiddenByCollapse])
|
||||
|
||||
// 使用 useLayoutEffect 在 DOM 更新后立即计算连线(替代 7 个 setTimeout)
|
||||
// offsetLeft/offsetTop 不受 scale 影响,无需依赖 scale
|
||||
useLayoutEffect(() => {
|
||||
calculateConnections()
|
||||
// 双 rAF 确保浏览器完成布局后再计算
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user