mcpField
M
micro-interaction

micro-interaction

github.com/iart-ai设计与 UI632.6k 次安装18 周前更新
代码仓库

关于 micro-interaction

用于实现功能性界面动效的设计技能,涵盖按钮反馈、抽屉和列表过渡。内容包括 CSS 与 Framer Motion 技术、时序、减少动态效果支持及按状态验证。

主要功能

  1. 01悬停、按压和焦点反馈
  2. 02进入与退出过渡
  3. 03列表和共享元素动效
  4. 04CSS 与 Framer Motion 模式
  5. 05减少动态效果指南

适用场景

  1. 01为按钮和开关添加反馈
  2. 02为提示、抽屉或模态框添加动效
  3. 03呈现加载与完成状态

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/iart-ai/web-animation-skills --skill micro-interaction

安装后在 Claude Code 中输入/micro-interaction确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
css-recipes.md
framer-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

完成前:

  1. 可独立打开——没有控制台错误,CDN 上的 React/motion(若使用)可正常加载。
  2. ?state=(或受控属性)冻结后,会呈现确定且稳定的状态。
  3. 已对多个状态进行截图——idle / active / open,与需求一致且没有异常表现。
  4. 遵循 prefers-reduced-motion——移除大幅度移动,保留不透明度/反馈。
  5. 缓动经过精心设计——进入使用 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:nonetransition-behavior: allow-discrete
无障碍性始终添加 prefers-reduced-motion 防护

参考文件

  • references/framer-motion-recipes.md — 变体与交错动画、AnimatePresence 模式、layout/layoutId Magic move、Reorder 拖动排序、拖动约束、手势组合,以及 useReducedMotion。
  • references/css-recipes.md — @starting-style、transition-behavior: allow-discrete、popover/dialog 退出动画、关键帧加载指示器/切换、用于可动画自定义属性的 @property,以及 prefers-reduced-motion 模式。

评论

支持 Markdown