关于 micro-interaction
用于实现功能性界面动效的设计技能,涵盖按钮反馈、抽屉和列表过渡。内容包括 CSS 与 Framer Motion 技术、时序、减少动态效果支持及按状态验证。
主要功能
- 01悬停、按压和焦点反馈
- 02进入与退出过渡
- 03列表和共享元素动效
- 04CSS 与 Framer Motion 模式
- 05减少动态效果指南
适用场景
- 01为按钮和开关添加反馈
- 02为提示、抽屉或模态框添加动效
- 03呈现加载与完成状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ iart-ai/ web-animation-skills --skill micro-interaction安装后在 Claude Code 中输入/micro-interaction确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/css-recipes.mdframer-motion-recipes.md详细介绍
微交互(UI 动效)
小巧实用的动效,让界面显得响应灵敏、鲜活:悬停/按下/聚焦反馈、切换、提示消息、抽屉,以及列表/布局动画。目标是提供反馈并保持连贯,而非装饰。
适用场景
- 悬停/按下/聚焦反馈;切换按钮、复选框、点赞/爱心按钮
- 提示消息/轻提示、抽屉、模态框、工具提示、手风琴(进入/退出)
- 添加/移除/重新排序列表;共享元素(“魔法移动”)布局过渡
- 加载 → 成功 → 错误状态过渡
原则(适用于所有动效)
- 时长:UI 微交互通常为 100–250ms。点击响应超过约 400ms 就会显得迟缓。
- 只对
transform和opacity应用动画——它们由 GPU 合成(不会触发布局/绘制)。避免对width/height/top/left添加动画;改用scale或布局动画。 - 提供即时的按下反馈:点击时应用
scale: 0.96,并使用快速弹簧效果。 - 采用非对称时序:进入稍慢(缓出),退出更快(缓入)。元素应优雅地出现,迅速地离开。
- 遵循
prefers-reduced-motion:限制非必要动效;保留透明度变化,移除大幅移动。 - 缓动默认值:进入使用缓出
cubic-bezier(0.16, 1, 0.3, 1);恰到好处的回弹使用cubic-bezier(0.34, 1.56, 0.64, 1);标准移动使用cubic-bezier(0.4, 0, 0.2, 1)。
Framer Motion(motion/react)基础
从 v11+ 开始,包的导入名称为 motion/react(framer-motion 名称仍然可用)。
手势与弹簧
import { motion } from "motion/react";
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.96 }}
whileFocus={{ boxShadow: "0 0 0 3px rgba(59,130,246,.5)" }}
transition={{ type: "spring", stiffness: 400, damping: 30 }}
/>
弹簧效果直觉:stiffness 越高,速度越快;damping 越高,回弹越少。stiffness: 400, damping: 30 是灵敏的 UI 默认值。若要明显回弹,可降低阻尼(例如 damping: 12)。
使用 AnimatePresence 实现进入/退出
退出动画要求用 AnimatePresence 包裹有条件渲染的子元素,并为每个子元素指定稳定的 key。
import { AnimatePresence, motion } from "motion/react";
<AnimatePresence>
{open && (
<motion.div
key="panel"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 8 }}
transition={{ duration: 0.2, ease: [0.16, 1, 0.3, 1] }}
/>
)}
</AnimatePresence>
对于提示消息/列表,使用 mode="popLayout",这样移除的元素在退出时不会占据空间。
布局动画(强大之处)
layout 会自动使用变换为任何布局变化(位置/尺寸)添加动画,非常适合重新排序、展开卡片以及网格<->列表切换。
<motion.li layout transition={{ type: "spring", stiffness: 500, damping: 40 }} />
跨组件共享元素过渡:为两个元素指定相同的 layoutId,Framer Motion 就会在一个元素挂载、另一个元素卸载时,将它们作为一个整体进行动画过渡。
{!open && <motion.div layoutId="card" onClick={() => setOpen(true)} />}
<AnimatePresence>
{open && <motion.div layoutId="card" />} {/* "magic moves" from the source */}
</AnimatePresence>
注意:普通的 layout 元素在缩放时会导致 border-radius 和文本变形。为需要抵消缩放效果的直接子元素添加 layout,并优先将 borderRadius/boxShadow 设为动效值,或使用 layout="position" 仅对位置应用动画。
纯 CSS 方案(无需 JS,包括离散属性)
现代 CSS 无需库即可为进入/退出添加动画,甚至可以为 display 切换添加动画。
.toast {
transition: opacity 0.2s ease, transform 0.2s ease;
/* allow animating to/from display:none and from initial render */
transition-behavior: allow-discrete;
}
.toast[hidden] { display: none; }
/* animate FROM these values on first render / when entering */
@starting-style {
.toast { opacity: 0; transform: translateY(8px); }
}
@starting-style 用于定义元素首次出现时的“打开前”状态值;transition-behavior: allow-discrete 让 display(以及弹出层/对话框的 overlay)参与过渡,从而在元素被移除前播放退出动画。Chrome/Edge/Safari 均已支持;旧版浏览器需提供不播放动画的降级方案。
务必添加减少动态效果的保护:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
状态,而非仅仅装饰
加载 → 成功 → 错误应表现为连贯的动效(例如按钮从旋转加载图标变为对勾),而不是突然切换。保持元素处于挂载状态,在不同状态间播放动画,让视线始终追踪同一个对象。
交付与验证(独立 HTML)
打包辅助脚本(
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3会冻结?t=N测试环境并截取每个时刻的屏幕截图;scripts/contact-sheet.sh sheet.png frame-*.png会将它们拼成一张图,便于一目了然地检查。参见scripts/README.md。
对于自包含的交互演示(切换、点赞按钮、提示消息、抽屉),交付物是一个可直接在浏览器中打开的 HTML 文件。纯 CSS 交互可直接交付;对于 Framer Motion 演示,可从 CDN(esm.sh)加载 React 和 motion,并写在一个内联模块中——无需构建步骤。一个文件正合适;不要使用打包工具。
输出要求:
- 一个
.html文件:包含你的标记,以及 CSS transitions/@starting-style或导入 CDN 上motion的内联<script type="module">。 - 提供一种方式,以便截图时呈现已确定的最终状态——交互由状态驱动,而不是由时间驱动,因此应冻结状态,而不是时钟。
定位测试环境 — 固定一个确定性状态。 微交互的“帧”就是它的状态(idle / hover / pressed / open)。加载时,?state=open 会应用目标状态,以便截图捕捉已稳定的界面:
<script>
const s = new URLSearchParams(location.search).get("state");
if (s) document.documentElement.dataset.state = s; // CSS keys off [data-state="open"]
// Framer Motion: set the controlled prop from `s` (e.g. const [open]=useState(s==="open"))
// For a CSS @keyframes loop instead, freeze it: el.style.animationDelay=(-N)+"s"; el.style.animationPlayState="paused";
window.__ready = true;
</script>
验证循环 — 渲染 → 设置状态 → 截图 → 检查: 打开每个有意义的状态(?state=idle、?state=hover、?state=open),截取屏幕截图,并检查保真度(按下反馈是否即时,卸载前退出动画是否已运行),以及异常表现(layout 是否扭曲了 border-radius/文本,提示消息退出后是否仍占据空间,是否出现 FOUC、卡顿)。任何无头工具都可以:
npx playwright screenshot --wait-for-timeout=400 "file://$PWD/demo.html?state=open" open.png
完成前:
- 可独立打开——没有控制台错误,CDN 上的 React/
motion(若使用)可正常加载。 ?state=(或受控属性)冻结后,会呈现确定且稳定的状态。- 已对多个状态进行截图——idle / active / open,与需求一致且没有异常表现。
- 遵循
prefers-reduced-motion——移除大幅度移动,保留不透明度/反馈。 - 缓动经过精心设计——进入使用 ease-out,退出使用 ease-in,时长控制在 100–250ms。
快速参考
| 需求 | 方案 |
|---|---|
| 按下反馈 | whileTap={{ scale: 0.96 }},spring 400/30 |
| 进入/退出 | AnimatePresence + initial/animate/exit |
| 重新排序/调整大小 | layout 属性(spring) |
| Magic move | 共享 layoutId |
| 提示消息堆叠 | AnimatePresence mode="popLayout" |
| 无 JS 进入动画 | @starting-style + transition |
| 动画切换为 display:none | transition-behavior: allow-discrete |
| 无障碍性 | 始终添加 prefers-reduced-motion 防护 |
参考文件
references/framer-motion-recipes.md— 变体与交错动画、AnimatePresence模式、layout/layoutIdMagic move、Reorder拖动排序、拖动约束、手势组合,以及useReducedMotion。references/css-recipes.md—@starting-style、transition-behavior: allow-discrete、popover/dialog 退出动画、关键帧加载指示器/切换、用于可动画自定义属性的@property,以及prefers-reduced-motion模式。