ChunLong Blog

ChunLong Blog

LOADING

漂流瓶手感重构记:一只瓶子引发的"考古"

2026-09-151123 字 · 约 4 分钟0 次阅读

漂流瓶架上线后我收到一条反馈:「拖瓶子的体验很糟糕」。

我当时的第一反应是调参数——灵敏度调高点、延迟调低点。但等我真正把代码翻出来逐行看,才发现这根本不是参数问题,而是四个实打实的 bug 加一套错误的手势设计叠在一起的结果。这篇文章记录整个"考古"过程。

现场一:瓶子永远追不上指针

拖拽的实现是 pointermove 里直写 transform,这没问题。问题出在这行:

className="bottle-item ... transition-transform duration-200 hover:-translate-y-1 ..."

这个 duration-200 是给 hover 上浮用的,但它对所有 transform 生效——包括拖拽时每一帧的直写。结果就是每次更新都被 200ms 的缓动平滑,瓶子以指数速度"追赶"指针,快一点移动就是明显的橡皮筋感。

拖拽态的 CSS 只加了阴影和 z-index,没人想起来关 transition。

WARNING

直写 transform 和 CSS transition 是天敌。任何"跟手"的交互,进入拖拽态的第一件事就是把 transition 关掉。

现场二:最自然的拖法根本进不了拖拽

原来的手势设计是这样的:

  • 轻点 = 拿起端详
  • 按住晃动 = 摇晃
  • 静止长按 350ms = 拖拽(移动超过 10px 就取消长按)

看出问题了吗?用户按下瓶子就移动——最自然的拖法——位移超过 10px,长按被取消,触发的判定是"摇晃"。想拖瓶子,必须按住完全不动等 350ms。而且那个 10px 是累计位移不是位移半径,指尖的微颤都会累加进去,触屏上几乎不可能满足。

这不是手感问题,是手势语义设计错了:摇晃和拖拽在抢同一种输入。

现场三与四:投掷从来没真正"弹"过

甩出瓶子的代码里有个致命顺序:松手时先清掉拖拽 transform,然后才去取瓶子的坐标当抛体起点。清掉 transform 后取到的是架子上原槽位的位置——所以瓶子会先瞬移回原格,再从那里"飞出去"。

还有更隐蔽的一个:

if (... || f.bounces > 4 || now - 0 > 60_000) settle();

now 是页面加载以来的毫秒数,now - 0 > 60_000 在页面打开超过一分钟后恒为真。这行本意是超时保险丝,写漏了起抛时间——结果弹跳动画第一帧就结束。也就是说,"甩出去会弹跳"这个我从没验证过的效果,从来没播放过。

TIP

永真/永假条件这类 bug 测试很难抓,视觉上是"功能存在但效果缺失"。每个声称存在的动效,都值得肉眼看一次完整播放。

重构:换一套手势语义

修 bug 之外,我重做了手势的层级结构,让它们不再抢戏:

手势行为
轻点端详(故事卡)
按住不动 220ms举起预备态(上浮 + 轻响)
按下后移动 >7px立即起拖
拖拽中快速左右甩摇晃彩蛋(不打断拖拽)
松手速度够快投掷

关键变化是摇晃从"竞争手势"降级成了"拖拽中的彩蛋":拖拽中检测水平速度方向在 400ms 内换向三次以上,就触发摇晃的视觉效果,拖拽继续。三种意图从此各走各的路。

架构上的一步:克隆拖影

最后是渲染架构。原来拖拽直接搬动真身元素,React 重渲染、换位重排都和 transform 直写互相干扰。重构后:

  • 按下拖拽时,把瓶子 cloneNode 成一个拖影——absolute 定位、transform 逐帧直写、完全不经 React;
  • 真身只 visibility: hidden 隐身占位,继续参与布局和换位重排;
  • 其他瓶子让位时做 FLIP 动画(记录旧位置,重排后用 WAAPI 平滑滑到新位置);
  • 换位加了 80ms 驻留滞回,指针压住目标瓶一小会儿才换,消除临界点的来回震荡。
// 拖影每帧:抓取点保持在指针下,倾角跟随水平速度
ghost.style.transform =
  `translate(${tx}px, ${ty}px) rotate(${tilt}deg) scale(1.12)`;

倾角随速度轻微倾斜(±8°,lerp 平滑),是成本最低但感知最强的"提着瓶子走"的活感。

复盘

这次最深的教训是:"手感差"几乎从来不是玄学。逐行读代码,每个"跟手性差"背后都能找到一个具体的技术原因——一个没关的 transition、一个过高的门槛、一个写反的顺序、一个永真的条件。参数解决不了设计错误,先考古,再动刀。

至于修完的效果?现在甩出去的瓶子,真的会弹跳了。页面开一小时再甩,也会。

相关阅读

评论