Motion
动效
动效不是装饰,而是对操作的回应。瑚琏将缓动曲线与时长收敛成 token,让 CSS 工具类和 motion 动画保持同一种手感。
先问:该不该动
按用户一天看到它多少次决定,而不是按好不好看决定。高频动作上的动画只会让软件显慢。
| 频率 | 典型场景 | 结论 |
|---|---|---|
| 每天 100+ 次 | ⌘K 命令面板、键盘快捷键 | 不做动画 |
| 每天几十次 | hover 态、列表导航 | 去掉或大幅削减 |
| 偶尔 | 对话框、抽屉、Toast | 常规动效 |
| 罕见 / 首次 | 引导、成功庆祝 | 可以有惊喜 |
Command 面板刻意不做缩放进场,只保留 150ms 淡入。键盘高频入口不应被位移动画拖慢。
缓动曲线
悬停轨道即可比较手感。默认 ease-out 被替换为更果断的统一曲线。
减速(默认档)
ease-outcubic-bezier(0.16, 1, 0.3, 1)进场 / 退场 / hover / 按压 —— 起步即最快,用户第一眼就看到位移,因此「感觉」比同时长的其它曲线更跟手。不确定用哪条时用它。
加速后减速
ease-in-outcubic-bezier(0.65, 0, 0.35, 1)元素在屏内位移 / 形变(不涉及出现与消失)。两端都平缓,像真实物体起步与停稳。
抽屉(iOS / Ionic)
--ease-drawercubic-bezier(0.32, 0.72, 0, 1)整屏尺度的滑入滑出:Drawer、ActionSheet。尾段比 ease-out 长得多,大面积位移才不会「冲到位再急停」。
不要使用 ease-in。它在用户最关注的开头阶段最慢,同样时长会比 ease-out 显得迟钝。
时长
交互动效控制在 300ms 内。180ms 的下拉比 400ms 的下拉感觉更快。
| Token | 时长 | 工具类 | 用于 |
|---|---|---|---|
| fast | 150ms | duration-150 | 按压反馈 / 贴身微交互(100–160ms) |
| base | 200ms | duration-200 | 浮层进出场:Tooltip、Popover、Select、Menu |
| slow | 300ms | duration-300 | 大块转场:Drawer / ActionSheet 面板滑动 |
| entrance | 600ms | duration-600 | 首屏逐级揭示 / 滚动入场(非交互反馈,可从容) |
按压反馈
任何可按下元素都应立即回应,否则界面像没有收到操作。
普通控件按下缩到 0.97,大卡片使用 0.99,避免子元素一起缩放时产生跳动。
// 已经使用 motion 的组件 import { pressable } from "@hulianui/ui"; <m.button whileTap={pressable.whileTap} transition={pressable.transition} /> // 不引入 motion 运行时的纯 CSS 方案 import { pressableClass } from "@hulianui/ui"; <button className={cn("…", pressableClass)} />
pressableClass 自带完整的 transition-property。请把它放在 cn() 末尾并替换原有 transition-colors。
浮层从触发器长出
锚定触发器的浮层应从触发器方向展开,而不是从自身中心展开。
/* Base UI 已在 Positioner 上提供原点,直接接入即可 */
<Popup className="origin-[var(--transform-origin)] data-[starting-style]:scale-95 …" />Dialog、AlertDialog 与 Modal 不锚定触发器,应保持中心原点。进场也不要从 scale(0) 开始;scale(0.95) 配合透明度更自然。
无障碍
减少动效不等于移除所有反馈。
在 prefers-reduced-motion 下保留帮助理解的透明度与颜色过渡,并去掉位移动画。