跳到主要内容

框选多选与变换手柄

点选只解决「命中最上层一个」。编辑器还要:拖空处画矩形选、Shift 加选、包围盒手柄、旋转、对齐吸附。这些全部是 场景数据操作,Canvas 只负责画装饰层。

CameraviewToWorld相机与锚点缩放

1. 框选:世界 AABB 相交,不要像素

export interface Aabb {
minX: number;
minY: number;
maxX: number;
maxY: number;
}

export function viewRectToWorld(
camera: Camera,
a: { x: number; y: number },
b: { x: number; y: number },
): Aabb {
const w1 = viewToWorld(camera, a.x, a.y);
const w2 = viewToWorld(camera, b.x, b.y);
return {
minX: Math.min(w1.x, w2.x),
minY: Math.min(w1.y, w2.y),
maxX: Math.max(w1.x, w2.x),
maxY: Math.max(w1.y, w2.y),
};
}

export function aabbHits(a: Aabb, b: Aabb): boolean {
return a.minX <= b.maxX && a.maxX >= b.minX && a.minY <= b.maxY && a.maxY >= b.minY;
}

从空处 pending 拖过阈值后进入 marquee。松手时对 pickable 节点的世界 AABB 做相交。旋转节点的 AABB 是 OBB 的轴对齐外包,会偏大——精确选集再对 path 做一次 isPointInPath 或 SAT。不要 getImageData 扫框。

Shift:结果与当前选集求并。Alt:求差。先后顺序写进产品规则,不要隐式。

2. 选集变换:改矩阵,不改 path 顶点

多选拖动 = 每个节点 local.e/f 加上 世界平移。若有父节点,要把世界 delta 变到父空间:

export function translateNode(
nodeLocal: DOMMatrix,
parentWorld: DOMMatrix,
worldDx: number,
worldDy: number,
): DOMMatrix {
const parentInv = parentWorld.inverse();
const delta = parentInv.transformPoint(new DOMPoint(worldDx, worldDy));
const origin = parentInv.transformPoint(new DOMPoint(0, 0));
return nodeLocal.translate(delta.x - origin.x, delta.y - origin.y);
}

缩放/旋转手柄:绕选集世界中心。手柄本身画在 装饰层,用 view 像素固定大小(handle = 8 / camera.scale 世界长度),否则缩小时手柄点不中。

命中顺序:手柄 > 节点 > 框选空处。手柄用正方形 AABB 即可,不必上 path。

3. 吸附

export function snap(value: number, grid: number): number {
return Math.round(value / grid) * grid;
}

export function snapThreshold(
value: number,
targets: readonly number[],
slopWorld: number,
): number {
let best = value;
let bestDist = slopWorld;
for (const t of targets) {
const d = Math.abs(value - t);
if (d < bestDist) {
bestDist = d;
best = t;
}
}
return best;
}

slopWorld 随相机变:8 / camera.scale,保证屏幕上大约 8 CSS 像素才吸。目标来自其它节点的边/中线,不要每帧对全部节点算——先用空间索引拿出视口附近。

Shift 拖:约束到水平或垂直(看 dx vs dy 谁大)。这是工具层,不是相机层。

4. 装饰层单独画

行军蚁、手柄、对齐参考线放动态层,每帧清。节点层只在 scene 变时重绘。见 分层

5. 失败形态

症状原因
旋转后框选漏选用了局部 AABB 去比世界框
手柄缩小时点不中手柄尺寸跟了世界 scale
子节点拖动方向反父级有翻转,delta 没进父空间
多选缩放各飞各的没有统一围绕公共中心
对齐抖slop 用了世界固定像素,放大后吸死

权威资料

核对日期:2026-08-26

需要 Camera / viewToWorld 时用 相机与锚点缩放