场域手记
NEXTFIELD / FIELD NOTESI — 交互
CHAPTER 01 / WRITINGFIELD NOTE 39

看起来像 bug 的设计,就是 bug

只要访客分不清「这是设计」还是「这坏了」,无论代码多正确,界面都已经失败了——以及怎么用可测量的方式提前发现它。

交互2026-09-233 分钟阅读

向下阅读

代码没有报错,类型检查通过,评审也没人提出异议。然后用户发来一张截图,说:这个字显示有问题。

打开一看,确实有问题——一个装饰图形压在字母上,两个字母的尾巴被切平。但更值得记下的是:在用户指出之前,我盯着这段代码看了很久,一直觉得它是对的。

意图必须被看见,而不是被解释

界面不是向编译器负责的,它向站在屏幕前的人负责。一个人看见一朵花盖住了字母,他的结论就是"这里坏了"。此时解释"这是刻意做的装饰"没有任何意义——解释的成本已经转嫁给了使用者。

这跟代码里的"能跑就行"是同一类问题,只是验收者从机器变成了人:

  • 编译器接受的东西,人未必接受
  • 能通过评审的代码,未必能通过眼睛
  • 解释得清楚的意图,不如不需要解释的呈现

所以判断标准要改:不是"这段代码是否符合规范",而是"这个画面是否把意图传达出去了"。

正确性不等于可读性

真正麻烦的是,这类问题往往不是"写错了",而是"写对了一半"。

替换元素不吃 inset。 用四个方向的 inset 把一个元素撑成居中的圆,对 div 成立,对 img 不成立。img 是替换元素,它取固有尺寸、锚在左上角,多余的约束被忽略。于是封面贴在左上角,还超出了容器。

留白对多数字母够用。 逐字入场动画需要裁切层,裁切层要留一点底部空白。常见写法给 0.06em,对没有下伸部的字母完全够用;但 y 和 g 的下伸部约 0.24em,尾巴直接被切平。

百分比在某个宽度下是精确的。 装饰用 right: 14% 定位,在一个窗口宽度下贴合得很准;但文字用 clamp(4rem, 14vw, 12.5rem) 缩放,装饰按容器缩放,两者比例不同。换个宽度,装饰就漂移到了别的字母上。

动画覆盖在多数时机是无害的。 悬停动效带 overwrite: "auto",在动画播完后再触发完全正常;但入场还没结束时触发,它会杀掉入场进度,把字母冻在半空。

这四条的共性是:它们只在特定输入、特定尺寸或特定时机下失效。静态审查几乎抓不到。

把"我觉得"换成可测量的数字

既然肉眼不可靠、代码审查也不可靠,那就换成能算出来的东西。

判断装饰有没有压住文字,比两个矩形:

const overlap = !(decor.left >= letter.right || decor.right <= letter.left);

判断字母有没有被裁切,用 canvas 的字体度量算出墨迹底部,再和裁切盒高度比较:

const ctx = document.createElement("canvas").getContext("2d");
ctx.font = `${weight} ${size} ${family}`;
const inkBottom = baseline + ctx.measureText("y").actualBoundingBoxDescent;

判断动效有没有在跑,读两次 transform 矩阵,看角度或模长是否变化。

这些都不难写,难的是养成习惯:在觉得"差不多"的时候,先去量一下。

把疑惑当成信号

"这里是不是坏了?"本身就是一条有效反馈,而且是最贵的那种——它意味着有人已经替你完成了验收,并且发现了问题。

收到这类反馈时,第一反应不该是解释设计初衷,而该承认:界面没有把意图传达出去。修的方式也不是加一句说明文字,而是让设计自己说清楚——装饰退开、字母完整、动效有始有终。

一个需要说明书才能被理解的界面,和一个确实坏掉的界面,在使用者那里的区别并不大。