UI 抛光
抛光来自于大量微小细节的叠加。本技能是这些值得关注的细节以及它们所取值的一个参考。
在设计评审时,请放慢界面的速度。在10%的速度下感觉不对劲的地方,在正常速度下就会以微妙的方式出错。
保留项目的组件库、令牌和密度,并根据其运动语言进行调整,除非以下某条规则规定了明确的交互方式。
下面每一条时长、曲线、缩放和模糊值都是具体的数值,而不是一个需要近似处理的范围。cubic-bezier(0.2, 0, 0, 1) 不是 cubic-bezier(0.4, 0, 0.2, 1),0.96 不是 0.95。请使用这里写明的值。
文本换行、字体渲染、表格数字和文本间距属于 better-typography。点击区域、焦点、键盘支持、ARIA 和减少动画属于 better-accessibility。分组、区块间距、断点和空间 RTL 属于 better-layout。
同心圆角
外半径 = 内半径 + 内边距。嵌套元素上不匹配的圆角是让界面感觉不协调的最常见原因。圆角、阴影和轮廓的配方在 surfaces.md 中。
光学对齐优先于几何对齐
当几何居中看起来不对时,进行光学调整。带图标的按钮、播放三角形和非对称图标都需要手动微调。
用阴影表示层次,用边框表示结构
如果边框的唯一目的是创造深度,那么优先使用多层透明 box-shadow 值。保留用于沟通结构或状态的边框:分隔线、分界线以及选中或焦点状态。
可中断的动画
对交互式状态变化使用 CSS 过渡,因为它们可以在动画中间被中断。保留关键帧动画给一次性运行的阶段性序列。
拆分并错开进入动画
对于不频繁的阶段性入场,如果序列用于沟通层次结构,则将内容拆分成语义块,并错开约 100 毫秒。动画化一个容器让你以相同的代价获得更少的效果。高频交互请保持无错开。参见 enter-exit.md。
微妙的退出动画
使用一个小的固定 translateY 而不是完整高度。退出应该比进入更柔和。双向都使用 ease-out。
上下文图标动画
使用 opacity、scale 和 blur 来动画化图标,而不是切换可见性。精确使用这些值:缩放 0.25 到 1,透明度 0 到 1,模糊 4px 到 0px。
如果使用了运动库(package.json 中的 motion 或 framer-motion),则匹配该包的导入路径,如果两者都存在,则匹配附近的导入路径。使用 transition: { type: "spring", duration: 0.3, bounce: 0 }。弹跳永远为 0。
如果没有运动库,则将两个图标都保留在 DOM 中,一个使用绝对定位,并使用 cubic-bezier(0.2, 0, 0, 1) 进行交叉淡入淡出。这提供了进入和退出效果,且没有依赖项。两种配方都在 icon-transitions.md 中。
图像轮廓
为图像添加一个低不透明度的 1px 轮廓,以获得一致的深度感。浅色模式下使用纯黑色(oklch(0 0 0 / 0.1)),深色模式下使用纯白色(oklch(1 0 0 / 0.1))。绝不能使用像石板灰或锌色这样的近似黑色,也绝不能使用带色调的中性色。带色调的轮廓会拾取底层表面的颜色,看起来像图像边缘的污垢。
按下时缩放
点击时使用 scale(0.96) 能赋予按钮触觉反馈。始终为 0.96;任何低于 0.95 的值都会显得夸张。添加一个 static 属性,以便在动画会分散注意力的地方将其关闭。参见 CSS、Tailwind 和 Motion 的配方。
页面加载时跳过动画
在 AnimatePresence 上使用 initial={false} 来阻止进入动画在首次渲染时执行。检查是否保留了有意的页面入场动画。
主题切换时抑制过渡
主题切换会同时更改几乎所有元素的颜色、背景、边框和阴影。这些属性上的每条过渡都会同时触发,导致切换过程变得模糊而非干脆利落。注入 *,*::before,*::after{transition:none !important},强制重排,然后在下一帧将其移除。参见 配方。
仅过渡发生变化的属性
始终指定确切的属性名称:transition-property: scale, opacity。Tailwind 的 transition-transform 涵盖了 transform, translate, scale, rotate。
谨慎使用 will-change
仅用于 transform、opacity 和 filter,这些属性 GPU 可以进行合成。绝不要使用 will-change: all。在看到首帧卡顿时才添加,而不是提前。参见 performance.md。
匹配图标描边与文本字重
图标旁边的文本携带了文本的光学重量:在常规(400)文本旁边使用 1.5px 描边,在半粗(600)文本旁边使用 2px。每个图标集使用一种描边重量,每个表面使用一个图标库。大小调整和 RTL 翻转在 icons.md 中。
单个 SVG,按状态重新着色
图标使用 currentColor,并通过 CSS 颜色和不透明度从悬停、选中和禁用状态获取颜色,绝不使用独立的资源。轮廓是默认变体;填充表示活动状态。
运动克制
为高频交互提供即时反馈,或者在透明度和颜色上使用时长不超过 150 毫秒的过渡。自定义动画每次触发都会产生注意力成本。
每个动画化的状态变化还需要一个静态提示:颜色、图标或标签。动画绝不是唯一的反馈通道。
完成前检查
| 错误 | 修复 |
|---|---|
| 图标看起来不居中 | 使用内边距进行光学微调,或修复 SVG |
| 突兀的阶段性入场或退出 | 错开不频繁的入场;保持退出微妙 |
| 主题切换导致整个页面交叉淡入淡出 | 在切换时禁用过渡,强制重排,在下一帧恢复 |
在元素上使用 transition: all | 指定确切属性 |
| 首帧动画卡顿 | 添加 will-change: transform(谨慎使用) |
| 纤细图标旁是粗体文本 | 使描边宽度匹配文本字重 |
报告
严重级别。 HIGH 会破坏交互、使动画无法使用,或者仅在动画运行时状态变化才可见。MEDIUM 是表面、图标或运动中可见的不一致。LOW 是孤立的抛光问题。
验证。 在没有浏览器的情况下:检查组件定义的每个状态,即悬停、焦点、活跃、加载和空状态,以及从代码中读取的运动持续时间和缓动曲线。在浏览器中:遍历每个状态,并在浏览器的动画面板中以 10% 的速度重放动画。将每个无法运行的检查报告为 Not verified。
格式。 将发现的问题按其违反的原则进行分组,按严重级别排序,每行对应一个根本原因,列出其出现的每个位置:
| 严重级别 | 位置 | 之前 | 之后 | 原因 |
|---|
位置 是 path/to/file:line。原因 说明所违反的原则和用户影响。
当有任何 HIGH 级别的问题存在时,以 Block 结束,否则以 Approve 结束,将表格中的其余问题留待处理。绝不要 Approve 你没有检查过的内容。如果没有需要报告的问题,则说明“未发现可操作的 UI 抛光问题”并报告验证情况。