漂流瓶手感重构记:一只瓶子引发的"考古"
漂流瓶架上线后我收到一条反馈:「拖瓶子的体验很糟糕」。
我当时的第一反应是调参数——灵敏度调高点、延迟调低点。但等我真正把代码翻出来逐行看,才发现这根本不是参数问题,而是四个实打实的 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、一个过高的门槛、一个写反的顺序、一个永真的条件。参数解决不了设计错误,先考古,再动刀。
至于修完的效果?现在甩出去的瓶子,真的会弹跳了。页面开一小时再甩,也会。
系列目录 · 10
相关阅读
技术· 2026/8/24
毛玻璃与粒子:这个博客的视觉配方
拆解本站的视觉与动效实现:分层背景、glass 工具类、CSS 关键帧粒子、Canvas 点击特效与 framer-motion。
技术· 2026/9/15
实验室扩容记:从一片星海到六个玩具
实验室起初只有一片 three.js 星海和一个烟花。这一轮一口气加了五个小玩具——更重要的是,加出了一套"新玩具十秒注册"的体系。
技术· 2026/9/10
在 2 核 2G 小水管上跑一个"特效很多"的博客
樱花粒子、Live2D 看板娘、Three.js 星海……这些和一台 2 核 2G 的小服务器听起来不该出现在同一个句子里。这是我的性能取舍笔记。