Appearance
放大的议会
背景:MenuStack 双面板绝对定位代码过于复杂,视觉放大后布局僵硬感加剧。
过程:重构为单 flex 滑动行 + 键盘导航 + 声明式快捷键 + 全局视觉放大。
上半场的整顿告了一段落。UI 改了,测试审了,环境搭了。但桌面壳知道,还有更深的坑藏在看不见的地方。
MenuStack 最近有点累。
作为联邦的议会,它统一了五套弹窗的导航样式。从模型库到动作库,从设置到场景,所有层级跳转都走它这一套 push/pop。它骄傲过 —— 消灭重复代码是它的勋章。
但最近,它开始觉得不对劲。
不对劲的地方有三:
第一,字太小了。12 像素的正文,11 像素的说明文字,用户眯着眼睛才能看清。app.css 说"设计趋势是小字显精致",但用户说"我看不清"。精致和看不清之间,MenuStack 站不住队。
第二,没有键盘导航。用户只能用鼠标点。对于一个管理着几十上百个模型的库来说,用鼠标一个个点过去,效率低得像用筷子夹豆子。
第三,双面板布局太绕了。SlideMenu 用两个绝对定位的面板 —— 当前面板和下一面板 —— 切换的时候一个左移一个右移,算位置、算宽度、算动画时序,代码像一团打了结的线。
这三个问题看起来不相干,但它们的病根是同一个——设计的格局太小了。
"该改改了," 用户说。
一、小字之困
字太小这件事,app.css 抵赖过一阵子。
"这是设计语言," 它说,"小字显高级,留白多,有呼吸感。你看那些大厂产品,正文都是 12、13px。"
"但用户看不清," main.ts 说,"用户坐在电脑前,离屏幕半米远,12px 的字像蚂蚁。他们要找模型,要翻菜单,要调参数。眼睛累。"
app.css 不服气。它翻出一堆设计文章,"留白""负空间""视觉层级"这些词像弹珠一样往外蹦。但它心里知道 —— 再好看的设计,看不清就是不好用。
真正让它松口的,是 menu.ts 说的一句话:
"你有没有想过,我们做的是工具,不是艺术品。"
工具的第一要义是好用。好用的第一步是看得清。
于是字号全线上调。正文从 12、13px 拉到 15、17px。说明文字从 11px 拉到 13px。按钮的内边距加大,从原来的紧巴巴变得宽松。滚动条从 4px 加粗到 6px —— 4px 的滚动条像一根头发丝,鼠标移上去要瞄准半天,6px 就舒服多了。
调完之后,app.css 站远了看。
"好像……也没那么丑?" 它犹豫地说。
"岂止不丑," main.ts 说,"用户现在不用眯眼睛了。这不比'高级'重要?"
app.css 没说话。但它偷偷把 --font-size-base 从 12px 改成了 15px,把 --scrollbar-width 从 4px 改成了 6px。它嘴上不说,但身体很诚实。
字号放大了,但这只是开始。更大的放大还在后面。
二、键盘能走的路,就不用鼠标
字号放大是面子工程,键盘导航才是里子。
MenuStack 以前是纯鼠标的。所有操作 —— 进菜单、退菜单、选条目、调滑块 —— 都要靠鼠标点点点。对于只有几个选项的设置页,这没问题。但对于模型库呢?几百个模型,翻页要滚鼠标滚轮,找到要点的再点一下。累。
"加键盘导航吧," main.ts 提议,"上下键选,左右键进退,回车确认。就像文件管理器那样。"
MenuStack 觉得有道理。但它很快发现了一个问题 —— 现在的 SlideMenu 是双面板绝对定位的。两个面板,各自有各自的列表,各自有各自的滚动。键盘聚焦在哪边?切换动画的时候聚焦怎么办?返回的时候聚焦要回到上一层的哪个条目?
"太绕了," MenuStack 说,"双面板的 DOM 结构太复杂,键盘导航没法干净地做。"
"那就改结构," main.ts 说。
改结构不是小事。双面板绝对定位的方案是当初花了好几天调出来的 —— 滑动顺滑,动画优雅。现在要推翻重来?
但键盘导航的价值摆在那里。如果 DOM 结构阻碍了功能,那结构就得改。
MenuStack 咬咬牙:"改。"
三、一行的事
新的结构很简单。
一个 slide-inner 容器,display: flex,flex-direction: row,宽度设成 200%。里面放两个 .slide-panel,每个宽度 50%。当前层级在左边,下一层级在右边。
切换层级的时候,给 slide-inner 加一个 transform: translateX(-50%) —— 整个容器往左移一半,右边的面板就露出来了。返回的时候去掉 translateX,容器滑回来,左边的面板重新显示。
就这么简单。
以前双面板绝对定位要算的那些东西 —— 两个面板的位置、宽度同步、动画协调 —— 现在全没了。一行 translateX 搞定。
一行 translateX 搞定——这是格局放大的好处:站高一层,问题就变小了。
MenuStack 看着新代码,半天回不过神。
"以前怎么没想到?" 它喃喃自语。
"因为你一开始就想复杂了," main.ts 说,"两个面板各管各的,当然复杂。把它们放进同一个容器,用容器的移动代替两个面板各自移动,就变成了一个问题,而不是两个。"
结构改完了,键盘导航就顺理成章了。
↑/↓—— 在当前面板的条目间上下移动,高亮跟随→/Enter—— 激活当前高亮项:是文件夹就进下一层,是操作就执行←—— 返回上一层
高亮条用 .slide-focused 类标记,背景色加深,外面一圈 outline。用户不用鼠标,只用键盘就能在整个菜单体系里穿梭。
像终端一样快。
MenuStack 用纯键盘操作了一遍模型库 —— 打开菜单、下翻三页、选中一个模型、进入详情、再退回来。全程手不用离开键盘。
"爽," 它说。
键盘导航把用户的操作半径放大了——从鼠标一个点,变成了整张键盘。
四、Ctrl+1~5 的重生
键盘导航改完了,main.ts 顺手把底部导航的快捷键也重构了。
以前的 Ctrl+1~5 是两套逻辑 —— 弹窗开着的时候,Ctrl+N 是"选中第 N 个条目";弹窗关着的时候,Ctrl+N 是"打开第 N 个菜单"。两套逻辑拧在一起,代码里全是 if (isAnyPopupOpen()) 的判断。
而且第二套逻辑(弹窗内选条目)有个致命问题 —— renderCustom 的面板里没有 .menu-item。滑块、按钮、颜色选择器,这些都不是标准的菜单条目,编号根本对不上。用户按了 Ctrl+2,什么也不发生,或者跳到了错的地方。
"这套逻辑从根上就不对," main.ts 说,"MenuStack 架构下,弹窗内容是动态的,条目编号根本没法稳定。"
解决方案很干脆 —— 砍掉第二套逻辑。
Ctrl+1~5 从此以后只干一件事:切换底部导航菜单的开/关。不管弹窗开没开,按 Ctrl+1 就是打开/关闭模型库,按 Ctrl+3 就是打开/关闭场景菜单。简单、直接、不纠结。
navActions 变成了一个声明式的数组 —— 每个按钮对应一个 id、一个 overlay、一个 toggle 函数。main.ts 照着数组绑定快捷键,不用再写一堆 switch case。
closeAllOverlays() 结束的时候顺便同步 aria-expanded 属性 —— 开了就是 true,关了就是 false。屏幕阅读器能读,无障碍也跟上了。
改完之后,main.ts 数了数代码行数 —— 少了三十多行。功能更强了,代码更短了。
快捷键的逻辑也被放大了——从两套拧在一起的逻辑,变成了一个干净的声明式数组。
这就是做对了的感觉。
五、滑块也有了脸
视觉放大的风潮吹到了滑块。
以前的滑块很朴素 —— 左边一个文字标签,右边一根滑块轨道,下面一个数值。能用,但没个性。每个滑块长一样,用户得仔细读左边的字才知道这是调什么的。
"给滑块加个图标吧," scene-menu.ts 提议,"就像按钮上的图标那样,一眼就知道是干嘛的。"
于是 addSliderRow 函数多了一个参数 —— icon。传一个图标名,滑块左边就多一个小图标。
"lucide:lightbulb" 是曝光,"lucide:contrast" 是对比度,"lucide:maximize-2" 是视场角,"lucide:droplet" 是颜色通道。
每个滑块都有了脸。
用户扫一眼,不用读字,大概就知道这一排滑块分别是管什么的。识别速度快了不止一倍。
识别范围也放大了——从读字才能认,变成扫一眼就知道。
scene-menu.ts 把所有散装的模式按钮也整理了一下。以前每个按钮都是用 style.cssText 硬写的样式 —— 内边距、边框、圆角、背景色、文字色,一坨 CSS 字符串塞在 JS 里,丑得像补丁。
现在统一用 .mode-btn 类,激活状态加 .active。样式全在 app.css 里管,JS 只负责加类、删类。干净。
app.css 收了这批样式,也没废话。它只是在 .mode-btn 的规则下面加了一行注释 —— 虽然它知道注释没人看,但它就是想记下来。
六、大一点,再大一点
全部改完那天,MenuStack 站在屏幕前,从头到脚打量自己。
字号大了。滚动条粗了。图标有了。键盘能走了。滑块有脸了。快捷键干净了。
它以前觉得"精致"就是好。小字、窄边距、细滚动条 —— 像个穿紧身衣的模特,好看,但喘不过气。
现在它宽松了。字大了,间距宽了,按钮好点了,键盘能用了。像换上了运动服 —— 不那么"精致"了,但舒服。
这不只是字放大了,是整个设计的格局放大了——从精致但局促,到宽松而好用。
工具的美,不在好看,在于好用。好用本身就是一种美。
app.go 从后端走过来,扫了一眼新界面。
"字是大了点," 它说,"但看着确实不累了。"
MenuStack 笑了笑。它知道这只是开始。联邦的界面还会继续改 —— 下一次可能是加搜索,可能是加多语言,可能是加主题。但有一件事它确定了:
以后再做设计决策,它会先问"用户用着累不累",再问"好不好看"。
顺序不能反。
它转过头,问 scene.ts:"环境系统做完了,UI 也改完了。下一步呢?"
scene.ts 看着屏幕上的模型,模型在樱花雨里旋转。
"下一步啊," 它说,"得想想,联邦还要收编什么。"
教训:顺序不能反——先问累不累,再问美不美。工具的第一要义是好用,大字、宽距、键盘能走,比「精致」的小字要紧得多。