最近在解决菜单的拖动手感问题,顺手把影响滚动手感的因素整理了一遍。
滚动是两段体验
最关键的认知是:滚动其实是两段体验。用户会把它们混在一起评价(“不跟手"“不流畅”),但两段的成因完全不同。
| 阶段 | 决定手感的因素 | 不参与的因素 |
|---|---|---|
| 手指按住时 | 延迟、1:1跟手 | 物理模型完全不参与 |
| 松手之后 | 减速物理、帧节奏 | 1:1已不重要 |
下面按这两个阶段来拆。
手指按住时:延迟与 1:1
位移必须严格 1:1
拖动期间,手指移动距离和UI移动距离必须是1:1的。画面必须是手指的延伸。
一旦不是1:1,大脑会立刻觉察到"这东西不完全听我的”,但用户往往说不出哪里不对,只会反馈"不跟手"。
延迟
从触摸到上屏的链路是:
触摸面板采样 -> 等屏幕同步信号 -> 上屏
这条链路加起来常常有几十毫秒。慢速拖动时延迟可能只体现为几个像素,看不出来;但快速滑动时,就是几十甚至更多像素的滞后。
延迟还受渲染链路本身影响:
- 帧成本高,延迟更大
- 渲染链路长且难以打断,也会明显拉高延迟
松手之后:物理惯性
松手后,惯性要像真实物体。
速度估计要准且稳
同一个甩动手势,必须每次都得到差不多的结果。速度通常取自抬手前最后二三十毫秒的瞬时速度。
如果估计方式不当(比如对整段拖动做固定窗口平均、或者窗口长度不确定),就会出现"有时候滑得远,有时候几乎不滑"。这种不可预期比单纯的慢更让人心烦。
减速模型要随速度变化
主流做法是指数衰减:速度每毫秒乘一个略小于1的系数,低于阈值就停止。
它的好处是行为自然涌现,而不是预设:轻轻拨动会很快停住,猛地甩动会滑更长时间——滑行时长是速度的结果,不是一个预设常数。
贯穿两段:帧节奏
帧间隔的稳定性
人眼对节奏不均匀的敏感度,远超对整体偏慢的敏感度。
稳定的30帧看起来是"流畅的慢";而60帧里偶尔插入一个30ms的长帧,会被明确感知为"卡了一下"。
动画的时间基准
掉帧无法完全避免,所以掉帧后如何处理很关键:动画应该直接跳到实际该到的位置,而不是整体变慢。
比如图标每1ms移动1px,如果掉了10ms,下次刷新应该直接到 +11px,而不是 +1px。
正确做法是:每帧读一次真实时钟,算出当前应该到的位置就直接跳过去。掉帧就掉帧,但运动的时间形状不变。
衔接两段:可打断性
内容正在惯性滑动时,手指按下必须立刻停止并接管,并且能马上再甩一次。
这一点是两个阶段之间的桥梁——如果打断不及时,用户会感觉界面在"跟自己抢方向盘"。