mcpField
T
to-spring-or-not-to-spring

to-spring-or-not-to-spring

github.com/raphaelsalaja设计与 UI42271 次安装今天更新
代码仓库

关于 to-spring-or-not-to-spring

用于按交互类型检查动画代码时序规则的 Skill,面向希望按文件行号和规则 ID 获取问题清单的开发者。

主要功能

  1. 01检查手势动画是否使用弹簧
  2. 02按动画用途检查缓动方式
  3. 03审查时长范围与上限
  4. 04标记应省略动画的场景
  5. 05按文件、行号和规则 ID 输出问题

适用场景

  1. 01审查拖动和滑动动画
  2. 02检查模态框与页面切换曲线
  3. 03审计界面代码中的动画时长
  4. 04查找输入或键盘导航动画

安装

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

$npx skills add https://github.com/raphaelsalaja/skill --skill to-spring-or-not-to-spring

安装后在 Claude Code 中输入/to-spring-or-not-to-spring确认已加载。

详细介绍

用弹簧还是不用弹簧

检查动画代码是否根据交互类型选择了正确的计时函数。

工作原理

  1. 读取指定文件(或提示用户提供文件/模式)
  2. 对照以下所有规则进行检查
  3. 以 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-gestures2高
easing-entrance-ease-out1中
duration-max-300ms3中

快速参考

交互时序类型
拖动释放弹簧stiffness: 500, damping: 30
按钮按压150msease
模态框进入200msease-out
模态框退出150msease-in
页面切换250msease-in-out
进度条不定linear
输入反馈0ms无

参考资料

评论

支持 Markdown