fix: v2.4 组合画布回归自由拖拽
- 移除拖动松手后的强制自动排列(恢复自由摆放能力) - 吸附升级为标准六边缘对齐: 拖动单元左/中/右×其余单元左/中/右 (上/中/下同理), 取最近者吸附并绘制辅助线 - 允许自由重叠摆放(用户自主决定布局) - 自动排列保留为可选整理按钮 - 参考 Konva/Fabric 吸附标准模式实现
This commit is contained in:
@@ -133,19 +133,30 @@ function mirrorUnit(uid: number) {
|
||||
}
|
||||
|
||||
// 吸附: 单元边缘贴近其他单元边缘时对齐(返回修正后的位置与辅助线)
|
||||
/** 六边缘对齐吸附(标准画布编辑器模式): 拖动单元的左/中/右、上/中/下与其余单元对应边比对, 取最近者吸附 */
|
||||
function snap(bb: BBox, excludeUid: number): { dx: number; dy: number; guides: { v: number[]; h: number[] } } {
|
||||
const TOL = 12
|
||||
const others = uids().filter(u => u !== excludeUid)
|
||||
.map(u => unitBBox(u)).filter(b => b) as BBox[]
|
||||
const gv: number[] = [], gh: number[] = []
|
||||
let dx = 0, dy = 0
|
||||
const myX = [bb.x0, (bb.x0 + bb.x1) / 2, bb.x1]
|
||||
const myY = [bb.y0, (bb.y0 + bb.y1) / 2, bb.y1]
|
||||
let bestX: { d: number; from: number; to: number } | null = null
|
||||
let bestY: { d: number; from: number; to: number } | null = null
|
||||
for (const o of others) {
|
||||
// 左缘贴对方右缘(无缝拼接)
|
||||
if (Math.abs(bb.x0 - o.x1) < TOL) { dx = o.x1 - bb.x0; gv.push(o.x1) }
|
||||
if (Math.abs(bb.x1 - o.x0) < TOL) { dx = o.x0 - bb.x1; gv.push(o.x0) }
|
||||
if (Math.abs(bb.y0 - o.y0) < TOL) { dy = o.y0 - bb.y0; gh.push(o.y0) }
|
||||
if (Math.abs(bb.y1 - o.y1) < TOL) { dy = o.y1 - bb.y1; gh.push(o.y1) }
|
||||
const oX = [o.x0, (o.x0 + o.x1) / 2, o.x1]
|
||||
const oY = [o.y0, (o.y0 + o.y1) / 2, o.y1]
|
||||
for (const a of myX) for (const b of oX) {
|
||||
const d = Math.abs(a - b)
|
||||
if (d < 12 && (!bestX || d < bestX.d)) bestX = { d, from: a, to: b }
|
||||
}
|
||||
for (const a of myY) for (const b of oY) {
|
||||
const d = Math.abs(a - b)
|
||||
if (d < 12 && (!bestY || d < bestY.d)) bestY = { d, from: a, to: b }
|
||||
}
|
||||
}
|
||||
if (bestX) { dx = bestX.to - bestX.from; gv.push(bestX.to) }
|
||||
if (bestY) { dy = bestY.to - bestY.from; gh.push(bestY.to) }
|
||||
return { dx, dy, guides: { v: gv, h: gh } }
|
||||
}
|
||||
|
||||
@@ -305,16 +316,11 @@ function onMove(e: MouseEvent) {
|
||||
|
||||
function onUp() {
|
||||
if (dragUnit) {
|
||||
const uid = dragUnit.uid
|
||||
const moved = dragUnit.moved
|
||||
guides = { v: [], h: [] }
|
||||
dragUnit = null
|
||||
if (moved && uid !== null && unitOverlaps(uid)) {
|
||||
// 拖动后与其他单元重叠 → 自动重新排列消除重叠
|
||||
autoArrange()
|
||||
return
|
||||
}
|
||||
render()
|
||||
if (moved) emit('change')
|
||||
return
|
||||
}
|
||||
pan = null
|
||||
@@ -368,21 +374,7 @@ function nudgeSelected(dx: number, dy: number) {
|
||||
emit('change')
|
||||
}
|
||||
|
||||
function rectsIntersect(a: BBox, b: BBox): boolean {
|
||||
return a.x0 < b.x1 - 1 && b.x0 < a.x1 - 1 && a.y0 < b.y1 - 1 && b.y0 < a.y1 - 1
|
||||
}
|
||||
|
||||
/** 单元是否与其它单元重叠 */
|
||||
function unitOverlaps(uid: number): boolean {
|
||||
const bb = unitBBox(uid)
|
||||
if (!bb) return false
|
||||
return uids().filter(u => u !== uid).some(u => {
|
||||
const o = unitBBox(u)
|
||||
return o ? rectsIntersect(bb, o) : false
|
||||
})
|
||||
}
|
||||
|
||||
/** 自动排列: 全部单元按 X 序紧贴排布、底边对齐(消除重叠) */
|
||||
/** 自动排列: 全部单元按 X 序紧贴排布、底边对齐(可选的整理工具,不强制) */
|
||||
function autoArrange() {
|
||||
const ids = uids().filter(u => unitBBox(u)).sort((a, b) =>
|
||||
(unitBBox(a)!.x0) - (unitBBox(b)!.x0))
|
||||
|
||||
Reference in New Issue
Block a user