玩一玩:触摸特效
黑色舞台、一颗发光的绿色光球,和你的手指。戳它、拖它、甩它——它像逗猫棒一样回应你:
- 光球会追着手指跑:阻尼弹簧物理,沿速度方向挤压拉伸(squash & stretch)。松手之后,它会晃晃悠悠地停在你把它丢下的地方。
- 水波纹在手指下持续泛开——整个拖动过程中每 ~150 ms 冒出一圈新涟漪,无限次触发。
- 烟花火花在按下和松开时炸开,拖动时还有一串火花尾迹跟着手指。支持多点触控:每根手指都有自己的水花。
这个页面上藏了一个开关
光球下方那行提示文字其实是个按钮。按一下,光球就不再停在原地,而是每次松手都弹回中间,标签也从 TOUCH·DRAG·DROP 变成 TOUCH·DRAG·FLICK。再按一下就换回来。
它不需要任何手势识别、也不需要计时器,这正是有意思的地方:标签自己绑了 main-thread-bindtouchstart/end,而触摸事件会冒泡,所以它的处理器一定跑在舞台的处理器之前。标签立起一个标志位,舞台看到就在这一次手势里全程让路——于是按开关既不会抓住光球,也不会溅起水花或放出告别烟花。
两行标签都由后台线程一次性渲染、叠在同一个位置;切换模式只是从主线程 crossfade 它们的 opacity。也就是说,连"界面上写着什么"都只花两次样式写入,全程不跨线程。
这个演示的核心就是触摸延迟,真机手感最佳。把上面的预览切到 二维码 标签,用 LynxExplorer 扫码——原生 main.lynx.bundle 就由本站直接下发。桌面端 Web 预览只响应触摸事件;请在浏览器 devtools 里开启触摸模拟(或直接用手机浏览器打开本页)。
为什么手感是"零延迟"的
页面上所有会动的东西——弹簧物理、涟漪圆环、两个粒子池(10 个涟漪 / 64 个火花)——全部运行在主线程上,由 'main thread' 主线程脚本和一个 requestAnimationFrame 循环驱动:
- 触摸处理器(
main-thread-bindtouchstart/move/end)在主线程执行,手指到像素之间没有任何跨线程往返。 - 元素池由后台线程一次性预渲染;每个池化元素绑定一个
MainThreadRef,引擎用setStyleProperty直写样式循环复用它们(只改transform和opacity,合成开销极低)。 - 引擎状态存放在一个值类型的
MainThreadRef里;光球归位、粒子全部熄灭后,rAF 循环会自动停车,下次触摸再被唤醒。 - 连 UI 状态——当前是哪种归位模式、正在显示哪行标签——也留在主线程。首屏渲染之后,这里没有任何东西需要再回后台线程走一趟。
最值得抄走的是"持续供给"这部分:手指按住期间,循环每帧生成尾迹火花、每 9 帧重新点一圈涟漪——所以无论你划多久、划多快,效果都不会中途断掉。
无头浏览器验证
示例自带 Lynx for Web 验证工具(examples/touch-fx/harness/),在无头 Chromium 里派发真实 CDP 触摸事件并做逐帧像素检查——包括一条连续性断言:4 秒拖动的每一个采样点上,特效像素都必须跟着当前手指位置: