← ALL NOTES
METHOD·2026.08·5 MIN READ
3D 测量项的整体移动手势设计
3d-visualization3d-design
问题

Scanat 支持复制测量项:用户在 3D 模型上完成一条长度或一块面积测量后,可以复制一份,再移动到新的位置继续使用,不必重新测量。

副本需要能够整体移动。问题在于,用户应该拖什么来完成这个动作?

一条长度测量包含两个真实几何端点,以及一个显示测量结果的数字标签,例如"328mm"。整体移动既可以由端点触发,也可以由标签触发,但两种方式对应不同的操作重点:前者更利于精确落点,后者不需要额外的模式切换。

我和产品经理最初分别倾向不同的方案。讨论之后才发现,分歧并不在于哪个手势"更自然",而在于两个不同的操作目标:降低模式成本,还是保证端点的精确落位。

两种方案的取舍
维度方案 A:拖端点方案 B:拖数字标签
精确落点强。手指或鼠标始终对应真实几何点,松手位置就是端点位置。弱。标签与端点存在偏移,需要目测补偿。
模式成本需要区分"调整端点"和"整体移动"。不需要额外模式,拖端点与拖标签天然区分。
界面占用需要提供模式入口。不需要额外 UI。
操作对象真实几何端点。系统生成的展示标签。

两种方案在单点调整上没有区别:拖动端点都只修改当前端点。需要决定的只有一件事:整体移动由什么触发。

把问题收窄之后,取舍也更明确。方案 B 的优势是简单,不需要引入模式;方案 A 的优势是精确,尤其适合需要把某个端点从 A 点准确移动到 B 点的场景。

最终方案

两套手势都先做进可运行原型,在 iPad、iPhone 和桌面浏览器上直接比较。最终方案的重点落在方案 A 的改造:保留端点作为整体移动的操作对象,但把原本常驻的模式开关改成按需出现。

  • 默认隐藏,不长期占用场景空间。
  • 点击底部工具栏的"移动"后展开。
  • 复制测量项后自动展开,并预选"整体移动"。
  • 开始拖拽或选中其他测量项后自动收起。

这样处理后,需要重新回答的已经不是"要不要模式切换",而是"模式切换是否需要一直显示"。模式本身仍然存在,但只在相关任务发生时出现。

交互模型

两套方案在实现上互斥,因此状态也需要明确表达这种从属关系。

场景内部使用 moveScheme: 'endpoint' | 'label' 决定当前采用哪一种整体移动方式;moveMode: 'endpoint' | 'whole' 只在端点方案下生效,用来区分单点调整和整体移动。

moveScheme = 'label'                     moveScheme = 'endpoint'
  │                                        │
  ├─ 拖端点 → 单点调整                     ├─ moveMode = 'endpoint' → 单点调整
  └─ 拖标签 → 整体移动                     └─ moveMode = 'whole'    → 整体移动

早期版本把这些状态拆成独立布尔值,出现过两套逻辑同时生效的冲突。改成从属状态后,任何时刻只有一条明确的交互路径。

同一个"整体移动"语义,在桌面和触屏上也不需要使用完全相同的触发方式:

  • 桌面端: 点击抓取 → 移动 → 再次点击放下。
  • 触屏端: 长按 450ms → 按住拖动 → 松手完成。

底层的位移和表面贴合逻辑相同,只把"什么时候进入拖拽状态"交给不同输入设备分别处理。

真机验证

真机测试暴露了两个静态设计稿里不明显的问题。

1. 背景变化后的文字对比度。 移动模式面板从深色胶囊改成白色悬浮卡片后,标题仍沿用了旧的浅灰文字颜色,在浅色背景上几乎不可见。这个问题与交互逻辑无关,但说明容器样式变化时,依赖背景的文字颜色也需要一起检查。

2. 新手势与已有行为冲突。 设计稿里原本写着"点击空白处收起面板"。但在 3D 场景中,点击空白区域已经绑定了"取消选中当前测量项"。

最后让两件事保持一致:取消选中时,面板自然收起。更重要的是,这次检查说明,像"点击空白处"这样的描述不能只看当前功能,还需要确认同一手势在场景里是否已经承担其他行为。

经验总结
  1. 先定义手势语义,再讨论操作对象。 "拖什么"之前,先确认用户是在调整一个点,还是移动一个整体。
  2. 互斥交互要有明确的状态层级。 理论上互斥的模式,不要在实现中变成多个可以同时开启的独立状态。
  3. 3D 交互需要在真实设备上验证。 静态稿很难暴露输入方式、已有场景手势和真实移动手感之间的冲突。
关于设计讨论

这次讨论里,我一开始理解错了产品经理的论据。我以为对方强调"端点是更合理的操作对象",后来才确认,他关注的是"用户希望把某个具体端点准确移动到目标位置"。两句话听起来接近,但对应的是不同的判断依据。

重新按这个前提比较后,端点方案的精确性才成为一个明确的设计优势。

对设计讨论来说,一个很实用的习惯是:在反驳之前,先把对方的判断复述一遍,确认双方讨论的是同一个问题。逻辑是否成立,取决于前提是否一致。

实现备注

复制后自动展开面板。 复制测量项时,内部先选中新副本并把 moveMode 重置为 'endpoint',随后在端点方案下再切到 'whole'。UI 没有通过 useEffect 根据 selectedId 猜测这次选中来自哪里,而是直接利用两个回调已有的固定顺序:先关闭,再由后续的模式变化重新打开。普通选中只触发第一步,因此最终仍保持关闭。

JAVASCRIPT
setSelected(copy.id);
if (state.moveScheme === 'endpoint') {
  state.moveMode = 'whole';
  callbacks.onMoveModeChange?.('whole');
}

拖拽开始时收起面板。 单点调整和整体移动最终都会经过 beginEndpointOrWholeMove()。因此"开始拖拽后收起面板"只需要挂在这个统一入口,不必分别写进鼠标点击、长按计时器等调用位置。

JAVASCRIPT
function beginEndpointOrWholeMove(id, index, clientPoint) {
  callbacks.onMoveInteractionStart?.();
  if (state.moveScheme === 'endpoint' && state.moveMode === 'whole') {
    // 整体移动
  } else {
    // 单点调整
  }
}
CONTINUE READING
从 Figma 组件属性到代码 Props2026.09Figma 设计系统的工程化落地2026.09