to-spring-or-not-to-spring
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTo Spring or Not To Spring
用Spring动画,还是不用?
Review animation code for correct timing function selection based on interaction type.
根据交互类型,审核动画代码以选择正确的计时函数。
How It Works
工作原理
- Read the specified files (or prompt user for files/pattern)
- Check against all rules below
- Output findings in format
file:line
- 读取指定文件(或提示用户输入文件/匹配模式)
- 对照以下所有规则进行检查
- 以格式输出检查结果
file:line
Rule Categories
规则分类
| Priority | Category | Prefix |
|---|---|---|
| 1 | Spring Selection | |
| 2 | Easing Selection | |
| 3 | Duration | |
| 4 | No Animation | |
| 优先级 | 分类 | 前缀 |
|---|---|---|
| 1 | Spring选择 | |
| 2 | Easing选择 | |
| 3 | 时长 | |
| 4 | 无动画 | |
Decision Framework
决策框架
Ask: Is this motion reacting to the user, or is the system speaking?
| Motion Type | Best Choice | Why |
|---|---|---|
| User-driven (drag, flick, gesture) | Spring | Survives interruption, preserves velocity |
| System-driven (state change, feedback) | Easing | Clear start/end, predictable timing |
| Time representation (progress, loading) | Linear | 1:1 relationship between time and progress |
| High-frequency (typing, fast toggles) | None | Animation adds noise, feels slower |
问:这个动效是响应用户操作,还是系统主动触发的?
| 动效类型 | 最佳选择 | 原因 |
|---|---|---|
| 用户驱动(拖拽、轻扫、手势) | Spring | 可在中断后继续,保留速度 |
| 系统驱动(状态变化、反馈) | Easing | 起止清晰,时间可预测 |
| 时间表示(进度、加载) | Linear(线性) | 时间与进度呈1:1对应关系 |
| 高频操作(打字、快速切换) | 无动画 | 动画会增加干扰,让操作感觉更慢 |
Rules
规则
Spring Selection Rules
Spring选择规则
spring-for-gestures
spring-for-gesturesspring-for-gestures
spring-for-gesturesGesture-driven motion (drag, flick, swipe) must use springs.
Fail:
tsx
<motion.div
drag="x"
transition={{ duration: 0.3, ease: "easeOut" }}
/>Pass:
tsx
<motion.div
drag="x"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>手势驱动的动效(拖拽、轻扫、滑动)必须使用Spring。
错误示例:
tsx
<motion.div
drag="x"
transition={{ duration: 0.3, ease: "easeOut" }}
/>正确示例:
tsx
<motion.div
drag="x"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>spring-for-interruptible
spring-for-interruptiblespring-for-interruptible
spring-for-interruptibleMotion that can be interrupted must use springs.
Fail:
tsx
// User can click again mid-animation
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ duration: 0.3 }}
/>Pass:
tsx
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>可被中断的动效必须使用Spring。
错误示例:
tsx
// 用户可在动画进行中再次点击
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ duration: 0.3 }}
/>正确示例:
tsx
<motion.div
animate={{ x: isOpen ? 200 : 0 }}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
/>spring-preserves-velocity
spring-preserves-velocityspring-preserves-velocity
spring-preserves-velocityWhen velocity matters, use springs to preserve input energy.
Fail:
tsx
// Fast flick and slow flick animate identically
onDragEnd={(e, info) => {
animate(target, { x: 0 }, { duration: 0.3 });
}}Pass:
tsx
// Fast flick moves faster than slow flick
onDragEnd={(e, info) => {
animate(target, { x: 0 }, {
type: "spring",
velocity: info.velocity.x,
});
}}当速度很重要时,使用Spring来保留输入的动能。
错误示例:
tsx
// 快速轻扫和慢速轻扫的动画效果完全相同
onDragEnd={(e, info) => {
animate(target, { x: 0 }, { duration: 0.3 });
}}正确示例:
tsx
// 快速轻扫的移动速度比慢速轻扫更快
onDragEnd={(e, info) => {
animate(target, { x: 0 }, {
type: "spring",
velocity: info.velocity.x,
});
}}spring-params-balanced
spring-params-balancedspring-params-balanced
spring-params-balancedSpring parameters must be balanced; avoid excessive oscillation.
Fail:
tsx
transition={{
type: "spring",
stiffness: 1000,
damping: 5, // Too low - excessive bounce
}}Pass:
tsx
transition={{
type: "spring",
stiffness: 500,
damping: 30, // Balanced - settles quickly
}}Spring参数必须平衡;避免过度振荡。
错误示例:
tsx
transition={{
type: "spring",
stiffness: 1000,
damping: 5, // 过低 - 过度弹跳
}}正确示例:
tsx
transition={{
type: "spring",
stiffness: 500,
damping: 30, // 平衡 - 快速稳定
}}Easing Selection Rules
Easing选择规则
easing-for-state-change
easing-for-state-changeeasing-for-state-change
easing-for-state-changeSystem-initiated state changes should use easing curves.
Fail:
tsx
// Toast notification using spring
<motion.div
animate={{ y: 0 }}
transition={{ type: "spring" }}
/>
// Feels restless for a simple announcementPass:
tsx
<motion.div
animate={{ y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>系统触发的状态变化应使用缓动曲线(Easing)。
错误示例:
tsx
// 提示通知使用Spring
<motion.div
animate={{ y: 0 }}
transition={{ type: "spring" }}
/>
// 对于简单的通知来说,会显得不稳定正确示例:
tsx
<motion.div
animate={{ y: 0 }}
transition={{ duration: 0.2, ease: "easeOut" }}
/>easing-entrance-ease-out
easing-entrance-ease-outeasing-entrance-ease-out
easing-entrance-ease-outEntrances must use ease-out (arrive fast, settle gently).
Fail:
css
.modal-enter {
animation-timing-function: ease-in;
}Pass:
css
.modal-enter {
animation-timing-function: ease-out;
}入场动效必须使用ease-out(快速进入,平缓收尾)。
错误示例:
css
.modal-enter {
animation-timing-function: ease-in;
}正确示例:
css
.modal-enter {
animation-timing-function: ease-out;
}easing-exit-ease-in
easing-exit-ease-ineasing-exit-ease-in
easing-exit-ease-inExits must use ease-in (build momentum before departure).
Fail:
css
.modal-exit {
animation-timing-function: ease-out;
}Pass:
css
.modal-exit {
animation-timing-function: ease-in;
}退场动效必须使用ease-in(先积累动量,再离开)。
错误示例:
css
.modal-exit {
animation-timing-function: ease-out;
}正确示例:
css
.modal-exit {
animation-timing-function: ease-in;
}easing-transition-ease-in-out
easing-transition-ease-in-outeasing-transition-ease-in-out
easing-transition-ease-in-outView/mode transitions use ease-in-out for neutral attention.
Pass:
css
.page-transition {
animation-timing-function: ease-in-out;
}视图/模式切换使用ease-in-out,以保持中性的注意力。
正确示例:
css
.page-transition {
animation-timing-function: ease-in-out;
}easing-linear-only-progress
easing-linear-only-progresseasing-linear-only-progress
easing-linear-only-progressLinear easing only for progress bars and time representation.
Fail:
css
.card-slide {
transition: transform 200ms linear; /* Mechanical feel */
}Pass:
css
.progress-bar {
transition: width 100ms linear; /* Honest time representation */
}线性缓动(Linear)仅用于进度条和时间表示。
错误示例:
css
.card-slide {
transition: transform 200ms linear; /* 机械感 */
}正确示例:
css
.progress-bar {
transition: width 100ms linear; /* 真实的时间表示 */
}Duration Rules
时长规则
duration-press-hover
duration-press-hoverduration-press-hover
duration-press-hoverPress and hover interactions: 120-180ms.
Fail:
css
.button:hover {
transition: background-color 400ms;
}Pass:
css
.button:hover {
transition: background-color 150ms;
}按压和悬停交互:120-180毫秒。
错误示例:
css
.button:hover {
transition: background-color 400ms;
}正确示例:
css
.button:hover {
transition: background-color 150ms;
}duration-small-state
duration-small-stateduration-small-state
duration-small-stateSmall state changes: 180-260ms.
Pass:
css
.toggle {
transition: transform 200ms ease;
}小型状态变化:180-260毫秒。
正确示例:
css
.toggle {
transition: transform 200ms ease;
}duration-max-300ms
duration-max-300msduration-max-300ms
duration-max-300msUser-initiated animations must not exceed 300ms.
Fail:
tsx
<motion.div transition={{ duration: 0.5 }} />Pass:
tsx
<motion.div transition={{ duration: 0.25 }} />用户触发的动画时长不得超过300毫秒。
错误示例:
tsx
<motion.div transition={{ duration: 0.5 }} />正确示例:
tsx
<motion.div transition={{ duration: 0.25 }} />duration-shorten-before-curve
duration-shorten-before-curveduration-shorten-before-curve
duration-shorten-before-curveIf animation feels slow, shorten duration before adjusting curve.
Fail (common mistake):
css
/* Trying to fix slowness with sharper curve */
.element {
transition: 400ms cubic-bezier(0, 0.9, 0.1, 1);
}Pass:
css
/* Fix slowness with shorter duration */
.element {
transition: 200ms ease-out;
}如果动画感觉缓慢,先缩短时长再调整曲线。
错误示例(常见误区):
css
/* 试图用更陡的曲线解决缓慢问题 */
.element {
transition: 400ms cubic-bezier(0, 0.9, 0.1, 1);
}正确示例:
css
/* 通过缩短时长解决缓慢问题 */
.element {
transition: 200ms ease-out;
}No Animation Rules
无动画规则
none-high-frequency
none-high-frequencynone-high-frequency
none-high-frequencyHigh-frequency interactions should have no animation.
Fail:
tsx
// Animated on every keystroke
function SearchInput() {
return (
<motion.div animate={{ scale: [1, 1.02, 1] }}>
<input onChange={handleSearch} />
</motion.div>
);
}Pass:
tsx
function SearchInput() {
return <input onChange={handleSearch} />;
}高频操作不应添加动画。
错误示例:
tsx
// 每次按键都触发动画
function SearchInput() {
return (
<motion.div animate={{ scale: [1, 1.02, 1] }}>
<input onChange={handleSearch} />
</motion.div>
);
}正确示例:
tsx
function SearchInput() {
return <input onChange={handleSearch} />;
}none-keyboard-navigation
none-keyboard-navigationnone-keyboard-navigation
none-keyboard-navigationKeyboard navigation should be instant, no animation.
Fail:
tsx
function Menu() {
return items.map(item => (
<motion.li
whileFocus={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
/>
));
}Pass:
tsx
function Menu() {
return items.map(item => (
<li className={styles.menuItem} /> // CSS :focus-visible only
));
}键盘导航应即时响应,无动画。
错误示例:
tsx
function Menu() {
return items.map(item => (
<motion.li
whileFocus={{ scale: 1.05 }}
transition={{ duration: 0.2 }}
/>
));
}正确示例:
tsx
function Menu() {
return items.map(item => (
<li className={styles.menuItem} /> // 仅使用CSS :focus-visible
));
}none-context-menu-entrance
none-context-menu-entrancenone-context-menu-entrance
none-context-menu-entranceContext menus should not animate on entrance (exit only).
Fail:
tsx
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}
/>Pass:
tsx
<motion.div exit={{ opacity: 0, scale: 0.95 }} />右键菜单(上下文菜单)入场时不应有动画(仅退场时可加)。
错误示例:
tsx
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0 }}
/>正确示例:
tsx
<motion.div exit={{ opacity: 0, scale: 0.95 }} />Output Format
输出格式
When reviewing files, output findings as:
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审查文件时,输出结果格式如下:
file:line - [规则ID] 问题描述
示例:
components/drawer/index.tsx:45 - [spring-for-gestures] 拖拽交互使用了Easing而非Spring
components/modal/styles.module.css:23 - [easing-entrance-ease-out] 模态框入场使用了ease-inSummary Table
汇总表格
After findings, output a summary:
| Rule | Count | Severity |
|---|---|---|
| 2 | HIGH |
| 1 | MEDIUM |
| 3 | MEDIUM |
输出检查结果后,需输出汇总表:
| 规则 | 数量 | 严重程度 |
|---|---|---|
| 2 | 高 |
| 1 | 中 |
| 3 | 中 |
Quick Reference
快速参考
| Interaction | Timing | Type |
|---|---|---|
| Drag release | Spring | |
| Button press | 150ms | |
| Modal enter | 200ms | |
| Modal exit | 150ms | |
| Page transition | 250ms | |
| Progress bar | varies | |
| Typing feedback | 0ms | none |
| 交互类型 | 时长 | 类型 |
|---|---|---|
| 拖拽释放 | Spring | |
| 按钮按压 | 150ms | |
| 模态框入场 | 200ms | |
| 模态框退场 | 150ms | |
| 页面切换 | 250ms | |
| 进度条 | 可变 | |
| 打字反馈 | 0ms | 无 |