☰
如何在100毫秒内让鼠标滚轮丝般顺滑:OpenLogi Smooth Scroll 运行时数学全剖析
2026/10/26 5:00:00 网站建设 项目流程

如何在100毫秒内让鼠标滚轮丝般顺滑:OpenLogi Smooth Scroll 运行时数学全剖析

【免费下载链接】OpenLogi⚡️A native, local-first alternative to Logitech Options+, written in Rust 🦀 — remap buttons, DPI, and SmartShift over HID++. No account, no telemetry.项目地址: https://gitcode.com/GitHub_Trending/op/OpenLogi

OpenLogi 是一个用 Rust 编写的原生本地优先工具,作为 Logitech Options+ 的无账号、无遥测替代方案。本文带你拆解它的Smooth Scroll(平滑滚动)运行时:一段 100 毫秒的三次缓动动画,如何被切成每 8 毫秒一帧的精确输出。无需任何数学背景,你只需要一个滚动的鼠标轮。🖱️

一、从"咔哒"到"丝滑":流水线总览

传统鼠标轮一次转动只产生一个离散的"滴答"(wheel tick)。OpenLogi 的平滑化流水线把它变成一条连续动画:

阶段职责关键文件
输入钩子捕获物理滚轮事件,非阻塞提交worker.rs
命令队列容量 128 的有界同步通道同上
滚动引擎纯数学状态机,逐帧计算位置scroll.rs
平台注入生成操作系统的合成滚轮事件macos.rs

一个值得注意的设计:只有"滴答式"滚轮输入会进入平滑模型。触摸板等像素级连续输入被明确拒绝(WheelDelta::try_from对Pixels类型返回错误),保持原生手感不被干扰。

二、核心数学:三次 smoothstep 缓动

每个滚动冲量(impulse)都会开启一个时长固定 100 毫秒的运动片段(ANIMATION_DURATION),位置由缓动函数求出(见 scroll.rs#L136-L141):

  1. 计算进度:progress = 已过时间 ÷ 100ms,并夹取到 0~1
  2. 应用三次 smoothstep:eased = p² × (3 − 2p)
  3. 输出位置:position = from + (target − from) × eased

smoothstep 的特点是首尾速度为零、中间最快——滚动有"加速—巡航—柔和收尾"的质感。用一个 1 个 tick 的冲量代入:

时刻progresseased 值累计滚动距离
25 ms0.250.156250.156 tick
50 ms0.50.50.5 tick
100 ms1.01.01.0 tick ✅

这些数字不是拍脑袋定的——它们就是 tests.rs 里synthetic_ratchet_trace测试断言的精确期望值,保证动画"分毫不差地停在对的位置"。

三、分阶段轮询:8 毫秒一帧是怎么驱动的

工作线程(名为openlogi-scroll)的循环非常巧妙,见 worker.rs#L352-L409:

  • 它查询引擎中所有活跃片段的下一个帧截止时刻(next_deadline);
  • 若无到期帧,就阻塞等待新命令;若有,则用recv_timeout(距截止的剩余时间)等待;
  • 超时触发帧推进——也就是说,帧时钟不是定时器的 tick,而是"命令接收的超时",零额外线程。

帧间隔为 8 毫秒(约 125 Hz,FRAME_INTERVAL)。更妙的是位置由绝对时间求值,而非累计帧数:即使某次唤醒被延迟,动画既不会变慢、也不会被拉长,只是少发一帧。测试synthetic_delayed_frames直接验证了"密集唤醒"与"延迟唤醒"累计位移完全相同。

四、Retargeting:连拨滚轮不丢距离

快速连续滚动时,新滴答到达而旧片段还没播完。引擎不会打断重来,而是执行retarget(重新瞄准):

  1. 用旧片段在"新滴答到达时刻"求出现在位置;
  2. 新目标 = 旧目标 + 新冲量;
  3. 从当前位置开一个全新 100 ms 片段滑向新目标。

若新旧冲量恰好抵消(如 +1.0 后紧跟 −1.0),目标等于当前位置,引擎直接返回Finished(0)——一帧都不输出,测试synthetic_opposing_impulses_cancel_before_output断言帧列表为空。距离守恒由emitted计数器精确记账,任何时刻累计输出都不会多滚或少滚。

五、输出相位机:Began / Changed / Ended / Cancelled

前台应用只能看到一条滚动相位流(多个设备可以并行滚动,但相位生命周期全局共享且配平)。四个相位定义在 inject.rs#L384-L393:

相位含义macOS 事件字段值
Began动画第一帧1
Changed中间帧(含 retarget 后)2
Ended收尾帧,携带精确修正4
Cancelled捕获源中途结束8

在 macOS 上,每帧被合成为像素单位的连续滚动事件(1 tick = 10 点),并打上SCROLL_PHASE字段,让系统按真实的平滑滚动手势来消费——惯性、渲染管线全部对齐。

六、兜底设计:绝不阻塞输入回调

物理输入回调必须"快速失败"而不是等待:提交使用try_send,队列满(128 条)时立即返回 false,回调改用直通注入。两个设置(平滑开关 + 1~100 的垂直灵敏度)被编码进同一个字节(高位=开关,低 7 位=灵敏度),保证读取到的一致快照。

灵敏度换算为乘数:乘数 = 灵敏度 ÷ 14(默认档 14 即 1×)。HID++ 拇指滚轮的平滑路径则不套用主滚轮灵敏度,且取消操作精确到单次会话,绝不牵连其他设备。

七、动手体验:一分钟开启平滑滚动

在配置中打开开关即可(见 config.example.toml 与 docs/CONFIGURATION.md):

smooth_scroll = true vertical_scroll_sensitivity = 14

想继续深挖?推荐阅读路线:

  • 引擎数学:crates/openlogi-agent-core/src/runtime/scroll.rs
  • 工作线程与队列:crates/openlogi-agent-core/src/runtime/scroll/worker.rs
  • 全部数学用例(都是可运行的算法夹具):crates/openlogi-agent-core/src/runtime/scroll/tests.rs
  • 平台注入层:crates/openlogi-inject/src/inject.rs

一句话总结:固定 100 ms 的 smoothstep 片段 + 绝对时间求值 + 距离守恒的 retarget,让"咔哒咔哒"的物理滚轮,在数学层面变成了一条精确、可取消、绝不超调的连续曲线。

【免费下载链接】OpenLogi⚡️A native, local-first alternative to Logitech Options+, written in Rust 🦀 — remap buttons, DPI, and SmartShift over HID++. No account, no telemetry.项目地址: https://gitcode.com/GitHub_Trending/op/OpenLogi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询