把多个独立功能合并进同一套界面时,最容易被低估的问题,不是"按钮够不够放",而是每个入口到底属于哪一种操作关系。
工具栏、顶栏、选中态操作条,表面上都是"点一下触发功能",但它们对应的用户前提完全不同:有的先进入一种模式,再去场景里操作;有的点击后立即执行;有的只有选中某个对象以后才有意义。
我之前把五个独立的 3D 交互 demo 合并成一个综合原型时,正好遇到这个问题。技术落地上,我和 Claude Code 一起把功能做成可注册的模块结构;但真正决定入口该放哪里的是产品语义,而不是代码长什么样。
这次最值得记下来的,是两次分类判断被纠正的过程。它们都说明了一件事:代码上相似的状态切换,在用户心智里可能属于完全不同的层级。
最终把界面入口分成三类:
| 区域 | 操作性质 | 典型功能 |
|---|---|---|
| 右侧工具栏 | 互斥工作模式。先选模式,再去画布中继续操作 | 选择、距离测量、体积测量、笔记、立方体 |
| 顶栏 | 一次性动作。点击后立即执行,不改变后续点击的含义 | 模型校正、旋转 15°、重置方向、重置视角、修改比例 |
| 选中态操作条 | 依赖当前选中对象。没有选中对象时入口本身不存在 | 测量项复制 |
这个分类的关键不在视觉位置,而在用户操作前提:
只要这个关系分清楚,功能放到哪里通常会自然很多。
第一个问题来自 cube-gizmo demo。
这个 demo 最初很容易被理解成一个常驻在屏幕上的移动端控制器。合并进综合原型时,Claude Code 也沿着这个思路,把 Gizmo 当成一个独立、长期存在的控制入口。
但重新走一遍真实使用流程以后,我纠正了这个判断。
Gizmo 并不是用来控制整个相机或场景的全局控制器。它服务的是某一个已经创建并选中的立方体。
正确流程应该是:
这里"立方体工具"和"Gizmo"不是同一层东西。
前者是一个工作模式,决定用户接下来在场景里做什么;后者是选中对象之后出现的子控件,只服务于当前对象。
如果把 Gizmo 做成常驻 overlay,界面仍然可以运行,但它会给用户一个错误暗示:这个控件像是在控制整个场景,而不是控制某个具体立方体。
最终实现上,Gizmo 也没有被硬塞进新的全局 slot,而是作为 feature 自己的选中态内容存在。
第二个问题来自俯视模式。
一开始合并时,它很自然地被当成工具栏里的另一个按钮,和选择、测量、笔记这些模式并列。
从代码结构看,这种判断很容易成立:它们都是"点一下以后切到某个状态"。
但从使用关系看,俯视并不是一种工作工具,而是场景的观察方式。
"当前使用什么工具"和"当前用什么视角看场景"是两条独立状态轴:
工具模式:选择 / 距离测量 / 体积测量 / 笔记 / 立方体
视角模式:3D / 俯视它们应该可以任意组合。
例如"俯视 + 距离测量"完全是合法状态。用户可以保持俯视,同时继续画测量线。
如果把俯视模式塞进工具栏的互斥集合,切换到测量以后就必须退出俯视,等于人为制造了一个产品上没有理由存在的限制。
因此最终把视角模式从 feature 工具体系里拆出来,放到场景 viewer 层,并在 UI 上使用独立的 3D / 俯视切换控件。
这两次错误看起来不同,但可以用同一组问题判断。
1. 点击以后,用户还需要去场景里做下一步吗? 如果需要,它更像一种工作模式,而不是一次性动作。
2. 这个入口是否依赖一个已经选中的对象? 如果依赖,它应该属于对象上下文,而不是常驻全局区域。
3. 它能不能和当前工具同时存在? 如果可以同时存在,就不应该被塞进同一组互斥状态。
4. 它改变的是"我要做什么",还是"我怎么看当前场景"? 前者通常属于工具,后者更接近 viewer 或场景级状态。
这些问题比"这个按钮看起来应该放左边还是右边"更早,也更重要。
这次具体的注册表结构和代码组织主要由 Claude Code 协助完成,但两次关键纠正都发生在产品语义层。
AI 很容易根据代码形态做出合理分类:都是按钮、都是状态切换、都可以注册到某个区域。从工程角度看,这些方案甚至没有明显错误。
真正需要人工确认的是:用户点击之前发生了什么,点击之后还要做什么,这个状态能不能和其他状态同时存在,以及这个控件到底属于场景、工具还是当前对象。
所以在这类综合原型里,我更倾向把 AI 用在"快速展开技术方案",再自己用真实操作流程去检查分类是否成立。
每个 feature 使用统一注册结构,并声明自己属于哪个 UI slot。
壳层分别渲染 toolbar、topbar、selection 三类入口。
Gizmo 不作为独立全局 slot,而作为 feature 的选中态内容存在,例如 SelectionGizmo。
视角模式不进入 feature 注册表,而归到 viewer / camera 层,因为它描述的是场景观察方式,而不是具体工作功能。