---
title: 90ms+90ms 确认律动与物理弹簧引擎
description: 为什么菜单点击后瞬间消失是错的？90ms+90ms 两拍式微交互确认律动与二阶阻尼物理弹簧引擎原理。
---

# 90ms+90ms 确认律动与物理弹簧引擎

点击反馈必须兼顾“确定感”与“极速流转”。TuffEx 总结了菜单动作确认节拍与连续物理弹簧动力学。

## 为什么菜单点击瞬间消失是错的？

在许多原生系统或网页中，用户在下拉菜单中点击某一项后，菜单在 0ms 内瞬间凭空消失。
用户常常会下意识地愣住一瞬：“我刚才真的点中那一行了吗？还是鼠标滑出去了？”
这种反馈缺失极大地削弱了界面的心理掌控感。

## 两拍式动作确认律动（90ms + 90ms）

TuffEx 的 `TxDropdownItem` 与 `TxContextMenuItem` 在点击后先走一段 **两拍、合计约 180ms 的确认节拍**，再关闭菜单：

```
[用户点击菜单项]
  │
  ├─【拍子一：90ms】这一行的边框、底色和阴影全部变透明，撤掉悬停与焦点的高亮。
  │
  ├─【拍子二：90ms】这一行切到 TxCardItem 的激活态，确认“已命中”。
  │
  └─【第二拍结束】派发 select 事件，然后关闭菜单。
```

两拍各由一个 90ms 的 `setTimeout` 推进；定时器不保证准点，实测每拍约 91ms。只有这次点击会关闭菜单（`close-on-select`）、并且 `activation-feedback` 开着（两者默认都开）时才走两拍，否则立即派发 select。当系统开启“减弱动态效果”时，同样立即执行。

:::TuffDemoWrapper{demo="ConceptMenuBeatDemo"}
:::

## 贝塞尔曲线在手势打断下的缺陷

传统 CSS 贝塞尔曲线（如 `ease-out`、`cubic-bezier`）本质上是**基于预设固定时长（Duration）的时间插值函数**。
如果在用户拖拽抽屉或移动卡片的过程中发生手势打断（例如用户突然反向滑动），过渡会从当前位置按固定时长重新走一条曲线，速度直接换成新曲线起点的斜率，与打断前的速度 $\vec{v}_0$ 无关：
- 起点平缓的曲线（如 `ease-in-out`）会先急停，再重新加速。
- 起点陡峭的曲线（如 TuffEx 的 `ease-out-strong`）反而会猛地加速。

两种情况速度都发生突变，运动轨迹出现机械的折断感，丧失物理真实感。

## TuffEx 二阶阻尼弹簧物理引擎

需要在打断时保住速度、又由 JS 逐帧绘制的动效，TuffEx 交给 `stepSpring` 推进，例如 TxSlider 拖过头后的回弹、TxMorph 与 TxTransitionSmoothSize 的尺寸形变，以及 TxLiquid。它积分的是二阶阻尼振荡微分方程（$x$ 为相对目标的位移）：
$$m \frac{d^2x}{dt^2} + c \frac{dx}{dt} + k x = 0$$
- **刚度（Stiffness $k$）**：控制弹簧形变后的回弹爆发力。
- **阻尼（Damping $c$）**：控制振荡衰减，消除反复晃动。
- **质量（Mass $m$）**：赋予卡片物理惯性。

`stepSpring` 用半隐式欧拉法积分，每 1/240 秒算一个子步。手势打断时只换目标，当前的位置与速度原样带过去，产生如同真实弹簧拉伸回弹般的丝滑跟手体验。

:::TuffDemoWrapper{demo="ConceptSpringInterruptDemo"}
:::
