关于 to-spring-or-not-to-spring
用于按交互类型检查动画代码时序规则的 Skill,面向希望按文件行号和规则 ID 获取问题清单的开发者。
主要功能
- 01检查手势动画是否使用弹簧
- 02按动画用途检查缓动方式
- 03审查时长范围与上限
- 04标记应省略动画的场景
- 05按文件、行号和规则 ID 输出问题
适用场景
- 01审查拖动和滑动动画
- 02检查模态框与页面切换曲线
- 03审计界面代码中的动画时长
- 04查找输入或键盘导航动画
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ raphaelsalaja/ skill --skill to-spring-or-not-to-spring安装后在 Claude Code 中输入/to-spring-or-not-to-spring确认已加载。
详细介绍
用弹簧还是不用弹簧
检查动画代码是否根据交互类型选择了正确的计时函数。
工作原理
- 读取指定文件(或提示用户提供文件/模式)
- 对照以下所有规则进行检查
- 以
file:line格式输出问题
规则类别
| 优先级 | 类别 | 前缀 |
|---|---|---|
| 1 | 弹簧选择 | spring- |
| 2 | 缓动选择 | easing- |
| 3 | 时长 | duration- |
| 4 | 不使用动画 | none- |
决策框架
请思考:这个动作是在响应用户操作,还是在表达系统状态?
| 动作类型 | 最佳选择 | 原因 |
|---|---|---|
| 用户驱动(拖动、轻扫、手势) | 弹簧 | 可承受中断,并保留速度 |
| 系统驱动(状态变化、反馈) | 缓动 | 起止明确,时序可预测 |
| 时间表示(进度、加载) | 线性 | 时间与进度保持 1:1 关系 |
| 高频操作(输入、快速切换) | 无 | 动画会增加干扰,让操作感觉更慢 |
规则
弹簧选择规则
spring-for-gestures
手势驱动的动作(拖动、轻扫、滑动)必须使用弹簧。
失败:
<motion.div
drag="x"
transition={{ duration: 0.3, ease: "easeOut" }}
/>
通过:
<motion.div
drag="x"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
spring-for-interruptible
可能被中断的动作必须使用弹簧。
失败:
// User can click again mid-animation
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ duration: 0.3 }}
/>
通过:
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>
spring-preserves-velocity
速度很重要时,使用弹簧保留输入能量。
失败:
// Fast flick and slow flick animate identically
onDragEnd={(e, info) => {
animate(target, { x: 0 }, { duration: 0.3 });
}}
通过:
// Fast flick moves faster than slow flick
onDragEnd={(e, info) => {
animate(target, { x: 0 }, {
type: "spring",
velocity: info.velocity.x,
});
}}
spring-params-balanced
弹簧参数必须保持平衡,避免过度振荡。
失败:
transition={{
type: "spring",
stiffness: 1000,
damping: 5, // Too low - excessive bounce
}}
通过:
transition={{
type: "spring",
stiffness: 500,
damping: 30, // Balanced - settles quickly
}}
缓动选择规则
easing-for-state-change
系统发起的状态变化应使用缓动曲线。
失败:
// Toast notification using spring
<motion.div
animate={{ y: 0 }}
transition={{ type: "spring" }}
/>
// Feels restless for a simple announcement
通过:
<motion.div
animate={{ y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>
easing-entrance-ease-out
进入动画必须使用缓出(快速到达,轻柔停稳)。
失败:
.modal-enter {
animation-timing-function: ease-in;
}
通过:
.modal-enter {
animation-timing-function: ease-out;
}
easing-exit-ease-in
退出动画必须使用缓入(离开前逐渐加速)。
失败:
.modal-exit {
animation-timing-function: ease-out;
}
通过:
.modal-exit {
animation-timing-function: ease-in;
}
easing-transition-ease-in-out
视图/模式切换使用缓入缓出,以保持中性的注意力引导。
通过:
.page-transition {
animation-timing-function: ease-in-out;
}
easing-linear-only-progress
线性缓动仅用于进度条和时间表示。
失败:
.card-slide {
transition: transform 200ms linear; /* Mechanical feel */
}
通过:
.progress-bar {
transition: width 100ms linear; /* Honest time representation */
}
时长规则
duration-press-hover
按压和悬停交互:120-180ms。
失败:
.button:hover {
transition: background-color 400ms;
}
通过:
.button:hover {
transition: background-color 150ms;
}
duration-small-state
小幅状态变化:180-260ms。
通过:
.toggle {
transition: transform 200ms ease;
}
duration-max-300ms
用户发起的动画不得超过 300ms。
失败:
<motion.div transition={{ duration: 0.5 }} />
通过:
<motion.div transition={{ duration: 0.25 }} />
duration-shorten-before-curve
如果动画感觉迟缓,应先缩短时长,再调整曲线。
失败(常见错误):
/* Trying to fix slowness with sharper curve */
.element {
transition: 400ms cubic-bezier(0, 0.9, 0.1, 1);
}
通过:
/* Fix slowness with shorter duration */
.element {
transition: 200ms ease-out;
}
不使用动画规则
none-high-frequency
高频交互不应使用动画。
失败:
// Animated on every keystroke
function SearchInput() {
return (
<motion.div animate={{ scale: [1, 1.02, 1] }}>
<input onChange={handleSearch} />
</motion.div>
);
}
通过:
function SearchInput() {
return <input onChange={handleSearch} />;
}
none-keyboard-navigation
键盘导航应立即响应,不使用动画。
失败:
function Menu() {
return items.map(item => (
<motion.li
whileFocus={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
/>
));
}
通过:
function Menu() {
return items.map(item => (
<li className={styles.menuItem} /> // CSS :focus-visible only
));
}
none-context-menu-entrance
上下文菜单显示时不应播放动画(仅退出时播放)。
失败:
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}
/>
通过:
<motion.div exit={{ opacity: 0, scale: 0.95 }} />
输出格式
检查文件时,按以下格式输出问题:
file:line - [rule-id] description of issue
Example:
components/drawer/index.tsx:45 - [spring-for-gestures] Drag interaction using easing instead of spring
components/modal/styles.module.css:23 - [easing-entrance-ease-out] Modal entrance using ease-in
汇总表
列出问题后,输出汇总:
| 规则 | 数量 | 严重程度 |
|---|---|---|
spring-for-gestures | 2 | 高 |
easing-entrance-ease-out | 1 | 中 |
duration-max-300ms | 3 | 中 |
快速参考
| 交互 | 时序 | 类型 |
|---|---|---|
| 拖动释放 | 弹簧 | stiffness: 500, damping: 30 |
| 按钮按压 | 150ms | ease |
| 模态框进入 | 200ms | ease-out |
| 模态框退出 | 150ms | ease-in |
| 页面切换 | 250ms | ease-in-out |
| 进度条 | 不定 | linear |
| 输入反馈 | 0ms | 无 |
参考资料
评论
支持 Markdown