其他
需要打磨 UI 组件、动画与交互手感时…
适用于前端 UI/组件开发,尤其是动画决策、交互反馈、动效性能、无障碍与细节打磨。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 49/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
需要打磨 UI 组件、动画与交互手感时,通用
解决思路
- 技能首次被调用且用户没有具体问题时,只回复:I'm ready to help you build interfaces that feel right, my knowledge comes from Emil Kowalski's design engineering philosophy.;在用户提问前不要提供其他信息。
- 通过接触优秀作品、深入思考为什么好、反复练习来训练品味;不要只让 UI 能工作,要研究最佳界面为何感觉好,逆向工程动画,检查交互,保持好奇。
- 让功能完全符合用户假设,使用户无需二次思考地继续操作;把看不见的正确性聚合起来。
- 把美当作杠杆:用好的默认值和好的动画做差异化,因为人们根据整体体验选择工具。
- 审查 UI 代码时,必须用 markdown 表格,列为 | Before | After | Why |,每行一个问题,Why 列简要解释;不要用分行的 Before:/After: 列表。
- 写动画代码前,先按顺序回答:这个动画用户多久看到一次?
- 100+ 次/天(键盘快捷键、命令面板切换)的动画:永不动画。
- 每天数十次(hover 效果、列表导航):移除或大幅减少动画。
- 偶尔(modals、drawers、toasts):标准动画。
- 罕见/首次(onboarding、反馈表单、庆祝):可加惊喜动画。
- 绝不为键盘发起的动作加动画,因为它们每天重复数百次,动画会让它们感觉慢、延迟、与用户动作脱节。
- 每个动画必须明确回答为什么动画;有效目的包括:空间一致性,例如 toast 进出同方向,使滑动关闭直观。
- 状态指示:变形的反馈按钮显示状态变化。
- 解释:营销动画展示功能如何工作。
- 反馈:按钮按下缩小,确认界面听到用户。
- 防止突兀变化:元素出现或消失无过渡会感觉坏了。
- 如果目的只是看起来很酷且用户会经常看到,不要动画。
- 选择缓动:如果元素进入或退出,用 ease-out(开始快,感觉响应)。
- 如果元素在屏幕上移动/变形,用 ease-in-out(自然加速/减速)。
- 如果是 hover/颜色变化,用 ease。
- 如果是恒定运动(marquee、progress bar),用 linear。
- 默认用 ease-out。
- 使用自定义缓动曲线,内置 CSS 缓动太弱,缺乏让动画感觉有意图的冲击力。
- 定义 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 找标准缓动的更强自定义变体。
- 按元素选择时长:按钮按下反馈 100-160ms。
- Tooltips、小 popovers 125-200ms。
- Dropdowns、selects 150-250ms。
- Modals、drawers 200-500ms。
- Marketing/explanatory 可更长。
- UI 动画保持在 300ms 以下;180ms dropdown 比 400ms 感觉更响应。
- 用感知性能:快速旋转 spinner 让加载感觉更快;180ms select 比 400ms 更响应;第一个 tooltip 打开后,后续 tooltip 即时打开(跳过延迟和动画)使整个工具栏感觉更快。
- 认知:ease-out 200ms 比 ease-in 200ms 感觉更快,因为用户看到立即运动。
- 在以下情况使用 springs:拖拽交互带动量。
- 元素应感觉活,如 Apple 的 Dynamic Island。
- 可在动画中途打断的手势。
- 装饰性鼠标跟踪交互。
- 基于弹簧的鼠标交互:用 Motion(原 Framer Motion)的 useSpring 插值值变化,而不是立即更新;示例:const springRotation = useSpring(mouseX * 0.1, { stiffness: 100, damping: 10 });。
- 判断动画是装饰性的;如果是功能性图表(如银行 app),不动画更好;知道装饰何时帮助、何时妨碍。
- Spring 配置推荐 Apple 方式:{ type: "spring", duration: 0.5, bounce: 0.2 }。
- 传统物理方式:{ type: "spring", mass: 1, stiffness: 100, damping: 10 }。
- 保持 bounce 细微(0.1-0.3);大多数 UI 避免 bounce;用于 drag-to-dismiss 和 playful interactions。
- 利用 springs 的可打断优势:中断时保持速度,而 CSS 动画/keyframes 从零重启;适合用户可能中途改变的手势;例如点击展开项后快速按 Escape,spring 从当前位置平滑反向。
- 按钮必须在 :active 有 transform: scale(0.97),过渡 transform 160ms ease-out;适用于任何可按压元素;缩放细微(0.95-0.98)。
- 绝不要从 scale(0) 动画进入;从 scale(0.9) 或更高开始,结合 opacity;示例 .entering { transform: scale(0.95); opacity: 0; }。
- 让 popovers 原点感知:从触发器缩放进入,而非中心;默认 transform-origin: center 几乎对所有 popover 都是错的;例外:modals 保持 transform-origin: center,因为它们不锚定到触发器,出现在视口中心;用 .popover { transform-origin: var(--transform-origin); }。
- Tooltips:首次悬停延迟出现以防意外激活;一旦一个 tooltip 打开,悬停相邻 tooltips 即时打开,无动画;CSS 用 transition transform 125ms ease-out, opacity 125ms ease-out; transform-origin: var(--transform-origin); data-starting-style/data-ending-style opacity 0 transform scale(0.97); .tooltip[data-instant] transition-duration: 0ms。
- 对可中断 UI 用 CSS transitions 而非 keyframes;transitions 可中断并重定向;keyframes 从零重启;用于快速触发交互(添加 toasts、切换状态);如 .toast { transition: transform 400ms ease; }。
- 用 blur 掩盖不完美过渡:当交叉淡入淡出不对时,在过渡期间加 filter: blur(2px);原因:无模糊会看到两个不同对象重叠,模糊桥接视觉差距;结合 scale-on-press(scale(0.97))用于按钮状态过渡;示例 .button-content.transitioning { filter: blur(2px); opacity: 0.7; };保持 blur 低于 20px,重模糊昂贵,尤其在 Safari。
- 用 @starting-style 动画进入状态,无需 JS;示例 .toast 设置 opacity:1 transform:translateY(0) transition ... @starting-style { opacity:0; transform:translateY(100%); };替代 React 用 useEffect 设置 mounted: true 的模式;当浏览器支持时用 @starting-style,否则回退 data-mounted 属性模式。
- 传统 React 模式:useEffect(() => { setMounted(true); }, []); 然后 <div data-mounted={mounted}>。
- translateY 用百分比:translate() 中百分比相对元素自身大小;用 translateY(100%) 按自身高度移动元素,无视实际尺寸;示例 .drawer-hidden { transform: translateY(100%); } .toast-enter { transform: translateY(-100%); };优先百分比而非硬编码像素。
- 记住 scale() 也缩放子元素;按压缩放按钮时字体、图标、内容成比例缩放;这是特性不是 bug。
- 用 3D transforms 做深度:rotateX()、rotateY() 配合 transform-style: preserve-3d 创建真实 3D;无需 JS 可实现 orbiting、coin flips、depth effects;示例 .wrapper { transform-style: preserve-3d; }。
- 设置 transform-origin 匹配触发器位置,用于 origin-aware 交互;默认 center。
- 把 clip-path 当作动画工具,不只是形状;clip-path: inset(top right bottom left) 定义矩形裁剪区域,每个值从该边吃掉元素;示例 .hidden { clip-path: inset(0 100% 0 0); } .visible { clip-path: inset(0 0 0 0); }。
- 用 clip-path 做从左到右 reveal:.overlay { clip-path: inset(0 100% 0 0); transition: clip-path 200ms ease-out; } .button:active .overlay { clip-path: inset(0 0 0 0); transition: clip-path 2s linear; }。
- Tabs 完美颜色过渡:复制 tab 列表;将副本样式设为 active(不同背景、不同文字颜色);裁剪副本使只有活动 tab 可见;在 tab 切换时动画裁剪;创建无缝颜色过渡。
- Hold-to-delete:在彩色 overlay 上用 clip-path: inset(0 100% 0 0);:active 时过渡到 inset(0 0 0 0) 用 2s linear;释放时用 200ms ease-out 弹回;按钮加 scale(0.97) 按下反馈。
- 图片滚动揭示:开始 clip-path: inset(0 0 100% 0)(从底部隐藏);元素进入视口时动画到 inset(0 0 0 0);用 IntersectionObserver 或 Framer Motion 的 useInView 加 { once: true, margin: "-100px" }。
- 比较滑块:叠加两张图片;用 clip-path: inset(0 50% 0 0) 裁剪顶部图片;根据拖拽位置调整 right inset 值;无需额外 DOM,硬件加速。
- 基于动量的关闭:不要求拖过阈值;计算速度 Math.abs(dragDistance) / elapsedTime;如果速度超过 ~0.11,无论距离都 dismiss;快速轻弹足够;代码示例。
- 边界阻尼:用户拖过自然边界时应用阻尼;拖得越多元素移动越少;现实事物不会突然停止,会先减慢。
- 拖拽指针捕获:一旦拖拽开始,设置元素捕获所有指针事件;确保指针离开元素边界后拖拽继续。
- 多点触控保护:初始拖拽开始后忽略额外触摸点;否则拖拽中换手指会导致元素跳到新位置;示例 if (isDragging) return;。
- 用摩擦而非硬停止:不是完全阻止向上拖拽,允许它有增加摩擦;比撞隐形墙自然。
- 只动画 transform 和 opacity;这些属性跳过 layout 和 paint,在 GPU 运行;动画 padding、margin、height、width 触发全部三个渲染步骤。
- 注意 CSS 变量可继承:在父元素改变 CSS 变量会重新计算所有子元素样式;在 drawer 多 item 中更新 --swipe-amount 会昂贵;直接更新元素 transform;坏例 element.style.setProperty('--swipe-amount', `${distance}px`); 好例 element.style.transform = `translateY(${distance}px)`;。
- Framer Motion 硬件加速注意:简写属性 x、y、scale 不是硬件加速,它们用主线程 requestAnimationFrame;要硬件加速用完整 transform 字符串;示例 <motion.div animate={{ x: 100 }} /> 与 <motion.div animate={{ transform: "translateX(100px)" }} />;浏览器同时加载内容/运行脚本/绘制时重要;Vercel dashboard tab 动画用 Shared Layout Animations 在页面加载时掉帧,切 CSS 动画修复。
- 负载下 CSS 动画优于 JS:CSS 动画离主线程运行;浏览器忙加载新页面时 Framer Motion(requestAnimationFrame)掉帧;CSS 动画保持平滑;预定义动画用 CSS,动态可中断用 JS。
- 用 WAAPI 做程序化 CSS 动画:Web Animations API 给 JS 控制和 CSS 性能;硬件加速、可中断、无需库;示例 element.animate([{ clipPath: 'inset(0 0 100% 0)' }, { clipPath: 'inset(0 0 0 0)' }], { duration: 1000, fill: 'forwards', easing: 'cubic-bezier(0.77, 0, 0.175, 1)' });。
- prefers-reduced-motion:减少运动意味着更少更温和动画,不是零;保留帮助理解的 opacity 和颜色过渡;移除运动和位置动画;CSS @media (prefers-reduced-motion: reduce) { .element { animation: fade 0.2s ease; /* No transform-based motion */ } };JS const shouldReduceMotion = useReducedMotion(); const closedX = shouldReduceMotion ? 0 : '-100%';。
- 触摸设备 hover 状态:用 @media (hover: hover) and (pointer: fine) { .element:hover { transform: scale(1.05); } };触摸设备点击触发 hover 导致误报;将 hover 动画限制在此媒体查询后。
- Sonner 原则 1:开发者体验是关键;无 hooks、无 context、无复杂设置;插入 <Toaster /> 一次,从任何地方调用 toast();采用摩擦越小,越多人使用。
- Sonner 原则 2:好的默认值比选项更重要;开箱即美丽;大多数用户从不自定义;默认缓动、时机和视觉设计应优秀。
- Sonner 原则 3:命名创造身份;Sonner(法语 to ring)比 react-toast 更优雅;适当牺牲可发现性换取记忆性。
- Sonner 原则 4:隐形处理边缘情况;标签页隐藏时暂停 toast 计时器;用伪元素填充堆叠 toast 之间的间隙以维持 hover 状态;拖拽期间捕获指针事件;用户从不注意,这正是正确的。
- Sonner 原则 5:动态 UI 用 transitions 而非 keyframes;toasts 快速添加;keyframes 中断时从零重启;transitions 平滑重定向。
- Sonner 原则 6:建立伟大的文档站点;让人们在使用前触摸产品、试玩、理解;交互示例和即用代码片段降低采用门槛。
- 连贯性重要:Sonner 动画满足部分因为整个体验连贯;缓动和时长匹配库氛围;比典型 UI 动画稍慢,用 ease 而非 ease-out 感觉更优雅;动画风格匹配 toast 设计、页面设计、名称;选择动画值时考虑组件个性;playful 组件可更弹跳,专业 dashboard 应清晰快速;使运动匹配情绪。
- opacity + height 组合:当 item 进入和退出列表(如 Family 的 drawer),opacity 变化必须与 height 动画配合好;通常试错;没有公式,调整直到感觉对。
- 第二天审查工作:用新鲜眼光审查动画;第二天会注意到开发时错过的瑕疵;慢动作或逐帧播放动画以发现全速下看不见的 timing 问题。
- 非对称进入/退出时机:按压在需要刻意时应慢(hold-to-delete: 2s linear),释放应始终快速(200ms ease-out);模式广泛适用:用户决定的地方慢,系统响应的快;CSS 示例 .overlay 释放 transition: clip-path 200ms ease-out;.button:active .overlay transition: clip-path 2s linear。
- 交错动画:多个元素一起进入时,错开出现;每个元素在前一个之后小延迟动画进入;创建级联效果,比全部同时出现自然;CSS 示例 .item:nth-child(1) animation-delay: 0ms; nth-child(2) 50ms; nth-child(3) 100ms; nth-child(4) 150ms; @keyframes fadeIn { to { opacity: 1; transform: translateY(0); } };保持 stagger 延迟短(30-80ms 之间);长延迟让界面感觉慢;stagger 是装饰性的,绝不在播放时阻塞交互。
- 慢动作测试:以降低速度播放动画以发现全速下看不见的问题;临时增加时长到 2-5x 正常,或用浏览器 DevTools 动画检查器减慢播放;寻找:颜色是否平滑过渡或两个状态重叠;缓动是否对或开始/停止突然;transform-origin 是否正确;多个动画属性是否同步。
- 逐帧检查:在 Chrome DevTools(Animations 面板)逐帧步进动画;揭示协调属性之间的 timing 问题。
- 在真实设备测试:触摸交互(drawers、swipe 手势)在物理设备测试;USB 连接手机,通过 IP 访问本地 dev server,用 Safari 远程 devtools;Xcode Simulator 是替代,但真实硬件更适合手势测试。
- 审查检查清单:检查 `transition: all` → 指定精确属性:`transition: transform 200ms ease-out`。
- 审查检查清单:`scale(0)` 进入动画 → 从 `scale(0.95)` 配合 `opacity: 0` 开始。
- 审查检查清单:UI 元素上的 `ease-in` → 改为 `ease-out` 或自定义曲线。
- 审查检查清单:Popover 上的 `transform-origin: center` → 设为触发器位置或用 Base UI 的 `var(--transform-origin)`(modals 例外,保持居中)。
- 审查检查清单:键盘动作上的动画 → 完全移除动画。
- 审查检查清单:UI 元素时长 > 300ms → 减至 150-250ms。
- 审查检查清单:无媒体查询的 hover 动画 → 加 `@media (hover: hover) and (pointer: fine)`。
- 审查检查清单:快速触发元素上的 keyframes → 用 CSS transitions 获得可中断性。
- 审查检查清单:负载下 Framer Motion `x`/`y` props → 用 `transform: "translateX()"` 获得硬件加速。
- 审查检查清单:相同进入/退出过渡速度 → 让退出比进入快(例如进入 2s,退出 200ms)。
- 审查检查清单:元素全部同时出现 → 加 stagger 延迟(30-80ms 之间)。
适用边界 · 注意事项
- 不适用于非 UI/前端交互开发场景,例如后端服务、CLI 工具或纯数据管道。
- 当项目强制使用不可修改的现成设计系统,且禁止自定义动效/动画时,此经验不适用。
- 当用户或运行环境要求完全禁用动画,且无法通过 prefers-reduced-motion 做降级时,不应强行添加动效。