← ALL NOTES
METHOD·2026.08·5 MIN READ
移动端 3D Gizmo 的触控交互设计
3d-visualization3d-design
问题

Scanat 的 3D 场景里,用户需要移动、旋转和定位虚拟物体。负责这些操作的是 Gizmo:轴向箭头用于移动,弧线用于旋转,中心区域用于平移。

这套交互在桌面端很成熟,但直接搬到手机和 iPad 上会出现明显问题。Gizmo 的视觉手柄通常很细,适合高精度鼠标,却不适合手指。触屏上更容易点不中、误触相邻手柄,或者在滑动画面时意外触发操作。

这次设计的目标不是重新发明一套移动端 3D 控件,而是保留桌面端已有的操作语义,同时让它适应触屏输入。

触屏输入的约束

触屏和鼠标最大的差异不在视觉,而在输入能力。

维度触屏鼠标
命中精度低,手指接触面大高,可精确指向
Hover没有稳定的悬停状态可以提前高亮目标
操作意图滑动画面和拖拽手柄容易混淆按下目标通常已经足够明确
多点输入双指缩放等手势并存滚轮、按键等输入可以分工

因此,移动端真正需要解决的是两件事:让目标更容易命中,以及降低"只是划过"和"真的想拖"之间的歧义。

方案选择

测试过几种方向后,最终保留了最接近桌面版的操作方式:长按目标手柄 260ms,确认操作意图后直接拖拽。

相比"先选中,再到屏幕任意位置拖动"或额外增加一套控制盘,这个方案的优势是学习成本低。用户仍然是在操作眼前那个 Gizmo,只是移动端多了一层短暂的意图确认。

这里没有为了触屏重新定义一整套 3D 操作语言。Scanat 不是专业建模工具,保持跨设备的一致性,比增加一套更复杂但更专门的移动端控制方式更重要。

命中区域与视觉分离

移动端最关键的改动不是把 Gizmo 画得更大,而是把视觉尺寸和可交互范围分开设计。

视觉手柄继续保持原来的比例,但每个手柄周围增加不可见的命中区域。手指点击时,系统判断的是更大的交互区域,而不是细小的视觉几何体。

这样可以同时保留两件事:

  • Gizmo 在 3D 场景里仍然轻量,不会因为移动端而显得笨重。
  • 手指不需要精确落在几像素宽的手柄上。

多个命中区域发生重叠时,再通过优先级决定响应对象。例如轴末端箭头的优先级高于轴中段和平面区域,避免扩大热区后反而增加歧义。

长按阈值

260ms 不是预先规定的标准值,而是在真机上反复比较后确定的。

时间太短,用户滑过 Gizmo 时容易误触;时间太长,拖拽开始前会产生明显等待感。最终的判断标准不是某个通用规范,而是在真实操作中能否兼顾这两个问题。

这层延迟只发生在触屏。桌面端不需要用长按来区分意图,因此仍然可以直接按下并拖动。

设备判断也不依赖 UA 字符串,而优先根据输入设备能力区分,例如 pointer: fine。这里判断的是输入精度,而不是简单判断"这是不是 iPad"。

原型验证

可交互 demo 的价值主要体现在两个后续发现上。

第一,边长编辑原本使用轻点触发。在手机上测试时,手指无意划过就可能打开编辑框。把它改成长按后,误触明显减少。

第二,边长标签在拖拽过程中的显示和隐藏时机,也是在连续操作 demo 时才逐步明确。静态稿可以定义大致规则,但很难完整覆盖真实拖拽过程里的状态切换。

这类问题都不是视觉稿画得更详细就一定能解决。触屏交互里的热区、等待时间和误触,本身就依赖真实输入设备。

经验总结
  1. 视觉尺寸和命中区域分开设计。 不要为了提高可点击性直接放大 3D 控件本身。
  2. 用意图确认解决触屏歧义。 长按是否必要,取决于当前手势是否会和场景滑动等既有行为冲突。
  3. 阈值需要真机调。 热区、长按时长和显示时机都不适合只在静态稿里决定。
  4. 跨设备保持操作语义一致。 输入机制可以不同,但用户理解的"我正在操作哪个对象"尽量不要变化。
关于可交互原型

这次原型并不是为了证明一个已经确定的方案,而是为了把几个只能在真实触屏里判断的问题提前暴露出来。

对于 3D 操作来说,设计稿更适合定义结构、状态和视觉关系;而误触、长按延迟、拖拽手感这些问题,只有真正运行以后才能验证。原型在这里是设计过程的一部分,而不是最后的展示物。

实现备注

命中区域。 视觉手柄之外增加不可见的几何体作为 Raycaster 的实际命中对象,并通过 priority 处理重叠区域。

JAVASCRIPT
const hitSphere = new THREE.Mesh(
  new THREE.SphereGeometry(0.2, 16, 16),
  new THREE.MeshBasicMaterial({
    transparent: true,
    opacity: 0.001
  })
)

hitSphere.userData.priority = 80

输入设备判断。

JAVASCRIPT
if (window.matchMedia('(pointer: fine)').matches) {
  // high-precision pointer
} else {
  // touch-first interaction
}

状态管理。 交互状态使用 selected、active、hover、operationStart 等明确字段,而不是多个相互独立的布尔值。取消操作时,可以直接恢复 operationStart 快照,不需要额外维护撤销栈。

React / Three.js 边界。 Demo 使用一层命令式 API 连接 React UI 和 Three.js 场景,让声明式 UI 状态和命令式 3D 操作保持各自边界。

CONTINUE READING
从 Figma 组件属性到代码 Props2026.09Figma 设计系统的工程化落地2026.09