fix(canvas): 画布连线 - 端点收敛到端口中点并新增流动光影

两件事,都在连线层。

一、多条线接同一个端口时不再沿着边散开。

原因是 use-canvas-connection-controller 把**鼠标松手的 Y 位置**记成了 anchorRatio,
于是每条线各自钉在落点上。判据还是反的:真正多端口的分镜脚本传 undefined,
而左右各只有一个出入口的普通节点却按落点比例取 Y,看起来像节点有很多端口。

改成单端口一侧永远取边的正中。这条规则有**两份实现**(canvas-connections 的
connectionHandleY 与 canvas-project-domain 的 getConnectionTargetAnchor),只改一处
会让实际画出来的线和吸附点对不上,所以两处一起改。同时把 anchorRatio 参数从这两个
几何函数上摘掉,让「不按落点」写在签名里而不是悄悄丢掉一个入参 —— 摘掉之后编译器
点出了 4 处仍在传旧参数的调用,正是这么做的收益。分镜脚本的 row: 句柄不受影响。

二、连线新增光晕与流光两层。

光晕(blur 的宽描边)和流光(一小段高亮沿路径跑)**只在悬停/选中态渲染**:blur 是
filter,成本随线条数量线性上升,常态几十条线全开会明显掉帧。常态仍是原来那条极淡的
虚线,只有指向的那条亮起来,顺带让「当前在看哪条链路」更清楚。

流光周期 2.1s 与虚线流动 1.25s 刻意错拍 —— 同频会锁成一条整体平移的虚线,
看不出有光在走。keyframes 的位移量等于 dasharray 之和(16 + 118 = 134),
否则循环处会跳一帧。

新动画已加入全部三处禁用名单:prefers-reduced-motion、.no-motion,以及
[data-canvas-viewport-interacting](平移缩放期间)。第三处最关键 —— 漏掉的话拖画布时
几十条线还在跑动画,正是错题本里「画布高频重绘导致崩溃」那一类。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dlh
2026-08-22 11:06:28 +08:00
parent 90d6c58de9
commit e07574ee14
5 changed files with 74 additions and 25 deletions

View File

@@ -165,17 +165,17 @@ function resetGenerationParamsOnModelSwitch(node: CanvasNodeData, patch: Partial
return { ...reset, ...patch };
}
export function getConnectionTargetAnchor(node: CanvasNodeData, current: ConnectionHandle, handleId?: string, scrollTop = 0, anchorRatio?: number) {
/**
* 连线落到目标节点上的吸附点。单端口一侧取边的正中——与 connectionHandleY 保持同一个
* 口径,否则吸附点和实际画出来的线会对不上(这两处是同一规则的两份实现,改一处必错)。
*/
export function getConnectionTargetAnchor(node: CanvasNodeData, current: ConnectionHandle, handleId?: string, scrollTop = 0) {
return {
x: current.handleType === "source" ? node.position.x : node.position.x + node.width,
y: storyboardHandleY(node, handleId, scrollTop) ?? node.position.y + node.height * normalizeAnchorRatio(anchorRatio),
y: storyboardHandleY(node, handleId, scrollTop) ?? node.position.y + node.height / 2,
};
}
function normalizeAnchorRatio(value?: number) {
return typeof value === "number" && Number.isFinite(value) ? clamp(value, 0.06, 0.94) : 0.5;
}
export function storyboardHandleAtY(node: CanvasNodeData, worldY: number, scrollTop = 0) {
const rows = node.metadata?.storyboard?.rows || [];
const localY = worldY - node.position.y - STORYBOARD_HEADER_HEIGHT;