Linear 的设计工程师 Emil Kowalski 最近开源了一套 AI Skills,专门解决一个让前端开发者头疼的问题:AI 写出来的 UI 代码功能没问题,但就是「差点意思」。
这条推文在 24 小时内拿到 1000+ 赞和 1000+ 收藏,说明踩这个坑的人不在少数。
问题出在哪
用 AI 写前端代码,最常见的体验是:它能跑,但看着别扭。
下拉菜单用了 ease-in 缓动,打开的时候感觉拖泥带水。弹窗从 scale(0) 开始放大,像凭空蹦出来的。按钮按下去没有任何反馈,像在按一块木板。Toast 通知用 keyframes 做入场动画,快速连续触发的时候动画互相打架。
这些细节单独看都不算 bug,但叠加在一起,整个界面就透着一股「凑合」的味道。
Emil 在 README 里写得很直接:Agents don't have great taste。AI 不知道进入动画该用 ease-out 而不是 ease-in,不知道按钮按下需要 scale(0.97) 的反馈,不知道 Framer Motion 的 x/y 简写属性在主线程繁忙时会掉帧。
他的解法:把领域经验编码成 Skill
Emil 的思路很朴素:既然 AI 缺乏设计品味,那就把品味写成规则喂给它。
他开源的 emilkowalski/skills 仓库包含 7 个 Skill,覆盖了设计工程的各个环节:
emil-design-eng:核心 Skill,编码了动画决策框架、组件设计原则、性能规则 review-animations:按 10 条不可妥协的标准审查动画代码 improve-animations:审计整个代码库的动画,输出优先级排序的修复计划 find-animation-opportunities:在 UI 中找到真正需要动效的地方 animation-vocabulary:用精确的词汇描述你想要的动效 apple-design:Apple 的界面设计和流畅动效原则,翻译到 Web 端 pick-ui-library:按任务类型推荐经过验证的 UI 库
安装一行命令搞定:
npx skills@latest add emilkowalski/skills
装完之后,你的 AI 编程助手在处理 UI 相关任务时,会自动加载这些规则。
动画决策框架:四个问题定生死
整套 Skill 的核心是一个动画决策框架。写任何动画代码之前,按顺序回答四个问题:
第一个问题:这个元素需要动画吗?
按使用频率判断。键盘快捷键、命令面板这类每天触发 100 次以上的操作,永远不加动画。Raycast 的命令面板就没有开关动画,因为对高频操作来说,动画只会让人觉得慢。
每天触发几十次的 hover 效果,要么去掉要么大幅缩减。偶尔出现的弹窗、抽屉、Toast,用标准动画。第一次使用的引导页、庆祝动效,可以加点惊喜感。
第二个问题:动画的目的是什么?
每个动画必须能回答「为什么动」。合法的目的包括:空间一致性(Toast 从同一个方向进出)、状态指示(按钮形变表示状态切换)、反馈(按下时缩小确认用户操作)、避免突兀变化(元素突然出现会让人吓一跳)。
如果答案是「看着酷」,而且用户会频繁看到,那就别动。
第三个问题:用什么缓动曲线?
进入/退出的元素用 ease-out,感觉灵敏。在屏幕上移动/变形的元素用 ease-in-out,加速减速自然。hover 和颜色变化用 ease。匀速运动(跑马灯、进度条)用 linear。
关键一点:永远别用 ease-in 做 UI 动画。它开头慢,让界面感觉迟钝。同样 300ms 的下拉菜单,ease-out 比 ease-in 感觉快得多,因为用户第一眼就看到元素在动。
Emil 推荐用自定义贝塞尔曲线,CSS 内置的曲线太弱了:
/* 强 ease-out,适合 UI 交互 */
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
/* 强 ease-in-out,适合屏幕内移动 */
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
/* iOS 风格的抽屉曲线 */
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
第四个问题:动画持续多久?
| 元素类型 | 推荐时长 |
|---|---|
| 按钮按压反馈 | 100-160ms |
| 工具提示、小弹窗 | 125-200ms |
| 下拉菜单、选择器 | 150-250ms |
| 模态框、抽屉 | 200-500ms |
核心原则:UI 动画控制在 300ms 以内。180ms 的下拉菜单比 400ms 的感觉灵敏得多。转得快一点的 loading spinner 让应用感觉加载更快,即使实际加载时间一模一样。
十条不可妥协的审查标准
review-animations 这个 Skill 定义了一套严格的动画审查标准。Emil 的态度是:默认打回,通过靠争取。
这十条标准覆盖了从合理性到性能的各个维度:
每个动画必须有理由——空间一致性、状态指示、反馈、解释、避免突兀 频率匹配——高频操作不加动画,低频操作可以加惊喜 缓动方向正确——进入/退出用 ease-out,ease-in做 UI 直接打回UI 动画 300ms 以内——超了必须有理由 变换原点正确——弹窗从触发点展开,不从中心(模态框除外) 可中断——快速触发的元素用 CSS transition 或弹簧动画,不用 keyframes 只动 transform 和 opacity——动 width/height/margin 直接打回 无障碍—— prefers-reduced-motion必须处理,hover 动画必须加媒体查询非对称时序——按下慢(用户在做决定),释放快(系统在响应) 整体协调——动效风格和组件个性匹配
性能陷阱:Framer Motion 的硬件加速坑
这套 Skill 里有一个很多人不知道的 Framer Motion 性能陷阱。
Framer Motion 的简写属性(x、y、scale)不走 GPU 加速,它们用 requestAnimationFrame 在主线程上跑。当浏览器同时加载内容、执行脚本、绘制页面时,这些动画会掉帧。
// ❌ 不走 GPU 加速,主线程繁忙时掉帧
<motion.div animate={{ x: 100 }} />
// ✅ 走 GPU 加速,主线程繁忙也流畅
<motion.div animate={{ transform: "translateX(100px)" }} />
Emil 提到他在 Vercel 做 dashboard 标签页动画时遇到过这个问题。用 Shared Layout Animations 做的切换在页面加载时掉帧,换成 CSS 动画(跑在合成线程上)就解决了。
CSS 动画天然跑在主线程之外。浏览器忙着加载新页面时,CSS 动画依然流畅,Framer Motion 动画会卡。预设动画用 CSS,动态可中断的动画用 JS,这是性能最优的搭配。
pick-ui-library:别让 AI 乱选库
pick-ui-library 解决的是另一个常见问题:AI 不知道用什么库,要么手写一个 Toast 组件,要么装一个没人维护的包。
这个 Skill 按任务类型给出明确推荐,全部是 Emil 自己用过的、信任的库:
| 任务 | 推荐库 |
|---|---|
| 无样式、可访问的 UI 组件 | base-ui |
| 命令菜单(⌘K) | cmdk |
| Toast / 通知 | Sonner |
| 验证码输入 | input-otp |
| 通用动画(弹簧、布局、进出场) | motion (Framer Motion) |
| 数字动画(计数器、价格) | NumberFlow |
| 实时/流式图表 | Liveline |
| 通用图表 | recharts |
| 拖拽 | dnd kit |
| 虚拟列表 | Virtuoso |
| 状态管理 | zustand |
| 条件 className | clsx |
| Tailwind 变体样式 | cva |
| 主题切换 | next-themes |
Skill 的设计原则是:识别任务,不是识别库名。用户说「我需要一个下拉框」,这是一个 UI 原语任务,应该推荐 base-ui,即使用户提到了别的库。先查 package.json 看项目已经用了什么,如果已经在用列表里的库,继续用它。如果用的是竞品,标记推荐但别擅自换依赖。
几个容易忽略的细节
这套 Skill 里有几个细节值得单独拿出来说。
按钮必须有按压反馈。 加 transform: scale(0.97) 在 :active 状态。这让 UI 感觉在响应用户。缩放要微妙,0.95-0.98 之间。
永远别从 scale(0) 开始动画。 现实世界中没有东西会从零突然变大。从 scale(0.95) 加 opacity: 0 开始,感觉自然得多。
弹窗的变换原点要感知触发位置。 下拉菜单应该从触发按钮的位置展开,不是从中心。Base UI 提供了 var(--transform-origin) 变量,直接用就行。模态框是例外,它从中心展开。
Tooltip 的延迟策略。 第一个 Tooltip 有延迟(防止误触),但一旦有一个 Tooltip 打开了,后续的应该立即显示、没有动画。这让人感觉整个工具栏都更快。
用 blur 遮盖不完美的过渡。 当两个状态的交叉淡入淡出感觉不对时,加 filter: blur(2px)。模糊把两个状态融合在一起,欺骗眼睛感知为一次平滑的变换。
对国内开发者的启示
Emil 的做法给国内 AI 辅助开发提了一个醒:AI 写代码的能力在快速提升,但「品味」这件事仍然需要人类专家来定义。
国内的 AI 编程助手(如 Cursor、Copilot 在国内的使用场景)同样面临这个问题。代码能跑,但 UI 细节粗糙。解决思路是一样的:把团队的设计规范、动效标准、组件选型偏好编码成 Skill 或 System Prompt,让 AI 在生成代码时自动遵守。
具体到实操,你可以做三件事:
整理一份团队 UI 规范文档,把缓动曲线、动画时长、组件选型写成明确规则 把这些规则喂给 AI,不管是通过 Skill 文件、System Prompt 还是自定义指令 建立审查清单,像 Emil 的十条标准一样,每次 AI 生成的 UI 代码都过一遍
领域专家知识 + AI 执行能力,这个组合比单纯依赖 AI 的通用能力有效得多。Emil 自己也说了:AI 不会取代领域专业知识,它会放大你能从中获得的东西。
资源链接
emilkowalski/skills GitHub 仓库[1] — 完整源码和安装说明 animations.dev[2] — Emil 的动画课程,更深入的讲解 Agents with Taste[3] — Emil 关于 AI 品味的文章 easing.dev[4] — 贝塞尔曲线可视化工具 base-ui[5] — 无样式可访问 UI 组件库 Sonner[6] — Toast 通知库,npm 周下载量 1300 万+
引用链接
[1] emilkowalski/skills GitHub 仓库: https://github.com/emilkowalski/skills
[2] animations.dev: https://animations.dev/
[3] Agents with Taste: https://emilkowal.ski/ui/agents-with-taste
[4] easing.dev: https://easing.dev/
[5] base-ui: https://base-ui.com
[6] Sonner: https://sonner.emilkowal.ski
夜雨聆风