SuperAPI AI 工具集

emil-design-eng

设计与创作

查看项目

更新于 10-16 11:26

技能介绍

用途

这是一个设计工程技能,源自 Emil Kowalski 的设计工程哲学,核心目标是帮助构建「感觉对」的界面。它关注 UI 打磨、组件设计以及那些用户不会刻意注意、但会共同决定软件品质的不可见细节。适用于决定是否添加动画、审查 UI 代码、设计按钮与弹层、处理手势与拖拽等场景。

核心哲学

  • 品味是可以训练的:好品味不是个人偏好,而是训练出的直觉。通过研究优秀作品、逆向分析交互动效、深入思考「为什么这样感觉好」来培养。
  • 不可见细节会累积:大多数细节用户永远不会意识到,这正是重点。当功能完全符合预期时,用户不会多想。大量不可见的正确性叠加,会创造出用户喜欢却说不清原因的界面。
  • 美是一种杠杆:人们选择工具基于整体体验,而非仅功能。好的默认值与好的动画是真正的差异化因素。

动画决策框架

在编写任何动画代码前,按顺序回答以下问题:

  1. 是否应该动画? 依据用户看到该动画的频率决定:每天 100+ 次(如键盘快捷键、命令面板切换)完全不动画;每天数十次(悬停效果、列表导航)移除或大幅减少;偶发(模态框、抽屉、Toast)使用标准动画;罕见/首次(新手引导、反馈表单、庆祝)可增加愉悦感。永远不要为键盘触发的操作做动画。
  2. 目的是什么? 每个动画都必须有明确目的,如空间一致性、状态指示、解释说明、反馈、防止突兀变化。如果目的只是「看起来很酷」且用户经常看到,则不要动画。
  3. 使用什么缓动? 进入/退出用 ease-out;屏幕内移动/变形用 ease-in-out;悬停/颜色变化用 ease;持续运动(跑马灯、进度条)用 linear;默认用 ease-out。关键是要使用自定义缓动曲线,内置 CSS 缓动太弱。提供了示例:--ease-out: cubic-bezier(0.23, 1, 0.32, 1);--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1)。永远不要在 UI 动画中使用 ease-in。参考 easing.dev 或 easings.co 获取曲线。
  4. 应该多快? 按钮按压反馈 100-160ms;工具提示、小弹层 125-200ms;下拉、选择 150-250ms;模态框、抽屉 200-500ms;营销/解释可以更长。规则是 UI 动画应保持在 300ms 以下。

感知性能

动画速度不仅关乎「敏捷感」,还直接影响用户对应用性能的感知:快速旋转的加载指示器让加载感觉更快;180ms 的选择动画比 400ms 感觉更响应;首个工具提示打开后的后续提示即时显示会让整个工具栏感觉更快。

弹簧动画

弹簧比基于时长的动画更自然,因为它模拟真实物理。适用场景:带惯性的拖拽交互、应感觉「有生命」的元素、可在动画中途被打断的手势、装饰性的鼠标跟随交互。使用 Motion(原 Framer Motion)的 useSpring 将视觉变化与鼠标位置插值关联,而不是立即更新。示例代码:

import { useSpring } from 'framer-motion';

// 无弹簧:感觉人工、瞬时
const rotation = mouseX * 0.1;

// 有弹簧:感觉自然、有动量
const springRotation = useSpring(mouseX * 0.1, {
  stiffness: 100,
  damping: 10,
});

弹簧配置推荐 Apple 方式:{ type: "spring", duration: 0.5, bounce: 0.2 };传统物理方式:{ type: "spring", mass: 1, stiffness: 100, damping: 10 }。弹跳保持轻微(0.1-0.3),大多数 UI 场景避免弹跳。弹簧在被打断时保持速度,而 CSS 动画会从零重启,因此更适合用户可能中途改变的手势。

组件构建原则

  • 按钮必须有响应感:在 :active 时添加 transform: scale(0.97),过渡为 transform 160ms ease-out。适用于任何可按压元素,缩放应轻微(0.95-0.98)。
  • 永远不要从 scale(0) 开始动画:从 scale(0.9) 或更高开始,结合透明度。例如 .entering { transform: scale(0.95); opacity: 0; }
  • 弹层要感知原点:弹层应从触发器缩放出现,而非中心。默认的 transform-origin: center 对几乎每个弹层都是错的。例外:模态框保持中心。Radix UI 使用 var(--radix-popover-content-transform-origin),Base UI 使用 var(--transform-origin)。
  • 工具提示:后续悬停跳过延迟:首个工具提示延迟显示防止误触,但一旦打开,悬停相邻提示应即时打开且无动画。
  • 可打断的 UI 用 CSS 过渡而非关键帧:CSS 过渡可被打断和重新定向,关键帧从零重启。对于可快速触发的交互(添加 Toast、切换状态),过渡更平滑。
  • 用模糊掩盖不完美的过渡:当两个状态交叉淡入淡出感觉不对时,在过渡期间添加细微的 filter: blur(2px)。模糊将两个状态混合,欺骗眼睛感知为单一平滑变换。模糊保持在 20px 以下,重度模糊性能开销大,尤其在 Safari 中。
  • 用 @starting-style 动画进入状态:无需 JavaScript 的现代 CSS 方式。示例:
.toast {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 400ms ease, transform 400ms ease;

  @starting-style {
    opacity: 0;
    transform: translateY(100%);
  }
}

当浏览器支持时使用 @starting-style,否则回退到 data-mounted 属性模式。

CSS Transform 精通

  • translateY 使用百分比:translate() 中的百分比相对于元素自身尺寸。用 translateY(100%) 按自身高度移动元素,无论实际尺寸如何。优先使用百分比而非硬编码像素值。
  • scale() 也会缩放子元素:与 width/height 不同,scale() 也会缩放子元素,按压缩放按钮时字体、图标、内容会成比例缩放。
  • 3D 变换实现深度:rotateX()、rotateY() 配合 transform-style: preserve-3d 可在 CSS 中创建真实 3D 效果,无需 JavaScript 即可实现环绕动画、硬币翻转等。
  • transform-origin:每个元素都有变换执行的锚点,默认为中心。根据触发器位置设置以实现原点感知交互。
  • clip-path 用于动画:不只是形状工具,是 CSS 中最强大的动画工具之一。inset 形状 clip-path: inset(top right bottom left) 定义矩形裁剪区域。可用于:标签页完美颜色过渡(复制标签列表,将副本样式设为「激活」,裁剪副本只显示激活标签,切换时动画裁剪)、长按删除模式(彩色覆盖层上用 clip-path: inset(0 100% 0 0),:active 时 2s linear 过渡到 inset(0 0 0 0),释放时 200ms ease-out 弹回)、滚动图片揭示(从 inset(0 0 100% 0) 开始,进入视口时动画到 inset(0 0 0 0),用 IntersectionObserver 或 Framer Motion 的 useInView)、对比滑块(叠加两张图,用 clip-path: inset(0 50% 0 0) 裁剪上面一张,根据拖拽位置调整右侧 inset 值)。

手势与拖拽交互

  • 基于动量的关闭:不要求拖拽超过阈值。计算速度:Math.abs(dragDistance) / elapsedTime。如果速度超过约 0.11,无论距离如何都关闭。示例代码:
const timeTaken = new Date().getTime() - dragStartTime.current.getTime();
const velocity = Math.abs(swipeAmount) / timeTaken;

if (Math.abs(swipeAmount) >= SWIPE_THRESHOLD || velocity > 0.11) {
  dismiss();
}
  • 边界阻尼:资料在「Damping at boundaries」处截断,后续内容暂未提供。

UI 代码审查格式

审查 UI 代码时,必须使用带 Before/After 列的 Markdown 表格,不要用分行列表。示例:

Before After Why
transition: all 300ms transition: transform 200ms ease-out 指定确切属性;避免 all
transform: scale(0) transform: scale(0.95); opacity: 0 现实中没有什么从虚无中出现
ease-in on dropdown ease-out with custom curve ease-in 感觉迟钝;ease-out 提供即时反馈
No :active state on button transform: scale(0.97) on :active 按钮必须对按压有响应感
transform-origin: center on popover transform-origin: var(--radix-popover-content-transform-origin) 弹层应从触发器缩放(模态框除外——保持居中)

限制

  • 具体浏览器兼容性细节、完整的速度阈值常量、边界阻尼的完整实现等资料在截断处暂未提供。
  • 使用 @starting-style 需考虑浏览器支持并准备回退方案。
  • 弹簧动画和模糊效果在性能敏感场景需谨慎使用。