☰
Mac Mouse Fix 滚动平滑与惯性调参实战:ScrollConfigTesting 文档解读与源码验证
2026/10/2 17:29:22 网站建设 项目流程
  • 桌面应用
  • 系统编程

【免费下载链接】mac-mouse-fix

Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad!

项目地址:https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix
点击查看免费下载

导读

Mac Mouse Fix(MMF)的滚动系统不是简单地"把滚轮刻度翻译成滚动事件",而是通过一套名为 HybridCurve(混合曲线)的动画算法,把每个滚轮 tick 模拟成"手指推动页面 + 惯性滑行"的物理过程。本文以仓库中真实的调参记录文档 ScrollConfigTesting.md 为核心骨架,结合 ScrollConfig.swift、Scroll.m、DragCurve.swift 等源码,系统讲解 MMF 滚动引擎的完整参数体系——包括pxPerTickBase、msPerStep、dragCoefficient、dragExponent、stopSpeed、BaseCurve等每个参数的物理含义、取值规律与调参手感,并给出可复用的"分档手感参数表"与屏幕高度自适应公式。读完本文,你将能够理解 MMF 的惯性滚动参数设计逻辑,并在自己的滚动算法或 MMF 配置中复现其"低惯性—顺滑—灵动"的手感分层。

一、为什么 MMF 的滚动需要一份"测试文档"?

鼠标滚轮硬件输出的是离散的 tick,而 macOS 原生的滚轮体验(尤其是 Apple Trackpad)输出的是连续的、带物理惯性的滚动。MMF 的滚动引擎要做的事,是把离散 tick 变成一条"有加速度、有惯性、有停靠感"的动画曲线。

这份 ScrollConfigTesting.md 就是 MMF 作者在 2022 年 12 月前后手工试出手感参数的完整记录:它没有华丽的理论,只有大量的"先设参数 → 滚动 → 感受 → 修改"实验循环,以及最终沉淀出的结论。文档明确说,这些参数最终"will appear in the app"(会进入应用成为默认配置),因此它实际上是 ScrollConfig.swift 中animationCurveParamsMap各曲线的参数来源(源码中直接注有For the origin behind these curves see ScrollConfigTesting.md)。

从源码结构看,MMF 的滚动动画由三类元素构成,理解它们是读懂本文所有参数的前提:

  1. BaseCurve(贝塞尔基础曲线):控制"页面跟随手指推动"阶段的运动形态,参数为四个控制点,如(0,0), (0,0), (1,1), (1,1)即线性曲线;
  2. DragCurve(拖拽/惯性曲线):控制松手后"惯性滑行"阶段的减速过程,由dragCoefficient、dragExponent、stopSpeed三个参数描述;
  3. HybridCurve(混合曲线):将二者拼接,先由 BaseCurve 驱动、再平滑过渡到 DragCurve。拼接逻辑见 HybridCurves.swift,其核心是在贝塞尔曲线上寻找一个"过渡点",使得两段曲线组合后覆盖的总距离等于本 tick 应滚动的像素数。

在 Scroll.m 中可以看到混合曲线的实际构造调用:

HybridCurve *hc = [[BezierHybridCurve alloc] initWithBaseCurve:baseCurve minDuration:baseDuration distance:delta dragCoefficient:pCurve.dragCoefficient dragExponent:pCurve.dragExponent stopSpeed:pCurve.stopSpeed distanceEpsilon:0.2];

而MFScrollAnimationCurveParameters正是 ScrollConfig.swift 中定义的参数载体,每个字段与测试文档中的配置一一对应。

二、核心参数逐个拆解:物理含义、默认值与取值范围

测试文档中的每一组"手感"都由以下参数组合而成。以下按参数维度整理其含义与实测规律。

2.1 pxPerTickBase / pxPerTickEnd —— 每个 tick 的滚动步长

  • pxPerTickBase:低速(单个 tick)时的步长,即每格滚轮刻度产生的基础像素位移。
  • pxPerTickEnd:高速(连续快速滚动)时步长会放大到的目标值,配合加速度曲线使用。

实测记录显示,步长直接决定"单 tick 是否干脆":文档指出"What makes the single steps feel so good is the large step size (aka pxPerTickBase)"(3.3 Mouse Remap 配置:pxPerTickBase: 120、pxPerTickEnd: 180)。步长越大,单格滚动越有"一步到位"的确认感;步长越小,越接近"像素级爬行"。

2.2 BaseCurve —— 基础曲线形状

文档中绝大多数配置使用BaseCurve: (0,0), (0,0), (1,1), (1,1),即 ScrollConfig.swift 中定义的ScrollConfig.linearCurve(源码注释特别提醒默认defaultEpsilon0.08 会让动画卡顿,因此显式使用 0.001)。唯一的例外是"5. No inertia"配置,其曲线为(0,0), (0,0), (0.5,1), (1,1),即一条前慢后快的加速曲线。

曲线形状决定了动画速度的"节奏":纯线性曲线速度恒定、跟手且可预测;带曲率的曲线则有缓入缓出感,但文档也记录了它带来的副作用——exp微调 0.1 就会产生巨大的手感差异(详见 2.4)。

2.3 msPerStep —— 每步动画的基准时长

  • 含义:BaseCurve 阶段的基本时长(毫秒)。时长越长,动画越"慢、飘、有惯性";越短,越"快、硬、跟手"。
  • 实测结论(文档原话):
    • 120ms 是"纯线性曲线仍保持响应感"的上限;
    • 110ms 是exp=1.0, coeff=30保持响应感的上限;
    • 140ms 起,高速滚动时内容移动变得"可以用眼睛跟随";
    • 在 ScrollConfig.swift 的 Xcode momentum 系列测试中:msPerStep: 205让单 tick 手感更佳,180 以下不再建议,280 以上会让单 tick 显得僵硬。
  • 反直觉结论:文档记录"Increasing the msPerStep actually makes the time taken for small flicks shorter"(2.3 Xcode momentum 3,msPerStep 从 200 提到 220 反而缩短了小甩动的耗时)——因为更长的基础时长给惯性段留出了更自然的过渡,小甩动整体更快完成。

源码佐证:在 Scroll.m 中,若baseMsPerStep != -1,则直接baseDuration = baseMsPerStep/1000.0;否则通过baseMsPerStepCurve曲线动态采样(见 2.6)。

2.4 dragExponent / dragCoefficient —— 惯性减速曲线的形状

这是文档中最敏感、作者花费最多篇幅实验的一组参数。惯性阶段的速度衰减由 DragCurve.swift 描述,其构造校验为assert(exponent >= 0)、assert(coefficient > 0)。

文档实测规律总结:

  • dragExponent决定减速曲线的凹凸形态。exp < 1.0 时曲线"先加速后减速"(时间上越滑越快再停下),exp > 1.0 时"快速衰减"。源码注释(ScrollConfig.swift)也记录了作者对 exp=0.7 能工作的困惑:"在 Desmos 上核对公式后我仍不明白它怎么能在 exponent < 1.0 时工作(但它确实有效)"。
  • 手感规律(文档原话):
    • 降低 exp → 单 tick 步长相对甩动变短;升高 exp → 单 tick 步长相对变长;
    • exp 极其敏感,0.1 的变化都感觉巨大;
    • 调整 exp 会整体改变曲线,必须大幅调整 coeff 来补偿;
    • 降低 coeff 应该更平滑,但有时反而让单 tick 更突兀。
  • 推荐甜点区间(文档):exp=1.0~1.05, coeff=15~23;exp=1.2可让单 tick 更顺滑、甩动更跟手,但exp=1.3无论如何补偿 coeff 都过犹不及。

在 Scroll.m 中,dragCoefficient、dragExponent、stopSpeed被直接传入 HybridCurve 构造器;而dragCoefficient的物理含义是"每帧速度衰减的力度系数",dragExponent是指数(速度越高衰减越剧烈)。

2.5 stopSpeed —— 惯性停止阈值(像素/秒)

  • 含义:当惯性速度衰减到低于stopSpeed(像素/秒)时,动画结束。
  • 实测规律(文档):
    • stopSpeed: 10是让110ms, exp=1.0, coeff=20不产生"像素爬行"(pixel creep)的最低值;
    • stopSpeed: 50会让单 tick 突然停止(abrupt),手感较差;
    • stopSpeed: 15在消除爬行的同时保持顺滑——最终"低惯性"配置普遍采用 15;
    • stopSpeed: 30是在 140ms 配置下进一步消除尾部爬行的选择(文档 EDIT 记录:"I also changed the stop speed to 30 on 6")。
  • 与 MomentumScroll 的关联:文档 2.2 指出,stopSpeed 可与 Xcode 生成的 momentumScroll 动画不同(因为我们希望主动截断动画)。

2.6 baseMsPerStepCurve —— 动态时长曲线(进阶)

文档中虽然未出现该参数名称,但 ScrollConfig.swift 明确解释:baseMsPerStepCurve用于在滚轮 tick 间隔变短时动态缩短动画时长,目前用于Smoothness: Regular(LowInertia)曲线。其采样逻辑在 Scroll.m:将timeBetweenTicks在[consecutiveScrollTickIntervalMax, consecutiveScrollTickIntervalMin]区间归一化后采样曲线,得到实际baseDuration。当前实现使用exp(x*4.0)的指数变形曲线,将时长从 180ms 映射到 110ms(ScrollConfig.swift)。

三、文档核心实战成果:三档手感参数表

测试文档的核心产出是"低 / 中 / 高"三档惯性(Low / Mid / High Inertia)的最终参数。逐条继承如下。

3.1 Low Inertia → 使用 "4.2 MMF 3"

参数值说明
pxPerTickBase60单 tick 基础步长
pxPerTickEnd90高速目标步长
BaseCurve(0,0),(0,0),(1,1),(1,1)线性曲线
msPerStep140基础动画时长
dragCoefficient23略低于 MMF 2 的 25,减少僵硬感
dragExponent1.0线性衰减
stopSpeed50快速停止,防止爬行

文档备注:这组参数"非常贴近 MMF 2 的手感但少一点僵硬"。在 ScrollConfig.swift 中可看到该曲线的实际落地(baseMsPerStepCurve版本)。

3.2 Mid Inertia → 使用 "3. Snappy"

参数值说明
pxPerTickBase60
pxPerTickEnd160高速步长大
BaseCurve(0,0),(0,0),(1,1),(1,1)线性
msPerStep180
dragCoefficient10衰减轻,滑行远
dragExponent1.1略高于 1,快速衰减
stopSpeed50

这组是"响应与滑行"的折中点,最终映射为kMFScrollAnimationCurveNameMediumInertia(虽然当前版本中该枚举分支为fatalError(),说明已被后续曲线替代,见 ScrollConfig.swift)。

3.3 High Inertia → 使用 "2.3 Xcode Momentum 4"

参数值说明
pxPerTickBase60
pxPerTickEnd160
BaseCurve(0,0),(0,0),(1,1),(1,1)线性
msPerStep205让单 tick 更顺滑
dragCoefficient40强衰减,滑行短
dragExponent0.7减速曲线(Trackpad 风格)
stopSpeed50

关键提示:文档指出,只要在 ScrollConfig.swift 中打开sendMomentumScrolls,这组参数就会被自动应用(当前源码中kMFScrollAnimationCurveNameHighInertia的sendMomentumScrolls: false,而HighInertiaPlusTrackpadSim为true,见 ScrollConfig.swift)。文档还记录 0.8 与 0.7 之争:真实 GestureScrollSimulator 使用的是30 / 0.7(而非最初以为的 0.8),0.7 偏飘,但恰好在 GestureScrollSimulator.m 中得到了源码证实(dragCoeff = 30.0; dragExp = 0.7;)。

3.4 其他有参考价值的候选配置

  • 2. Trackpad-style(msPerStep: 200, coeff: 30, exp: 0.8, stop: 50):文档评价"非常好,几乎肯定采用",理由是它精确复刻了 Apple Trackpad 驱动曲线,甚至可以发送 MomentumScroll 事件获得出色的 overscroll 效果。
  • 4. MMF(msPerStep: 110, coeff: 20, exp: 1.0, stop: 50):用于模拟旧版 MMF 滚动算法。
  • 4.1 MMF 2(msPerStep: 140, coeff: 25, exp: 1.0, stop: 50):"很僵硬,但中速滚动更平滑"。
  • 5. No inertia(msPerStep: 250, coeff: 999999, exp: 99999, stop: 99999):用极端值"关闭"惯性,文档甚至坦言"当前反而更喜欢这个"。

四、文档中的调参方法论:从"手感对照"到"甜点收敛"

这份文档最有价值的其实是调参方法。其流程可以概括为:

  1. 设定基准曲线:所有候选先用同一线性 BaseCurve 起跑,隔离其他变量;
  2. 甜点枚举:固定大部分参数,只扫一个维度(如 msPerStep 依次取 110/120/140/180/205/220),记录每个值的手感关键词("floaty"、"abrupt"、"responsive"、"stiff");
  3. 交叉对比(Face off):把候选两两 A/B,保留胜者再挑战下一候选。文档中完整记录了三次对决:
    • 候选 1(110ms, exp=1.0, coeff=20, stop=15)vs 候选 3(110ms, exp=1.2, coeff=17, stop=15)→ 3 胜(1 太飘);
    • 3 vs 4(110ms, exp=1.05, coeff=22, stop=15)→ 4 胜(3 太直接);
    • 4 vs 5(110ms, exp=1.05, coeff=17, stop=15)→ 5 胜;
    • 5 vs 6(140ms, exp=1.05, coeff=15, stop=15)→ 6 "完胜"(Wins HARD),作者评价它兼具 MMF 0.9 的顺滑与 MMF 1/2 的响应。
  4. 以版本为标尺:文档频繁以"MMF 2 手感""MMF 0.9 手感"作为参照物校准,并把 MMF 3 的 step size 与 MMF 2 默认档对齐,用于解释为何更高的平滑度是合适的。
  5. 记录反直觉现象:例如"coeff 从 17 降到 10 反而更不平滑"、"exp 微调 0.1 手感剧变"——这些记录对后续维护者极具价值。

五、加速度设置与屏幕自适应公式

文档后半部分解决的是"pxPerTickEnd 如何随屏幕尺寸变化"。它先通过多组屏幕实测得出"手感好"的 pxPerTickEnd 区间:

  • 2160p 屏幕 + Xcode Momentum 4:最大 180;最小(1080p 下)80;
  • 2160p + Snappy 2:最大 130;1080p 下 110;
  • MMF 手感(846p 屏):80。

随后文档给出了一个自构造的经验公式(原文完整保留):

pxPerTickEnd = pxPerTickEndBase * inertiaFactor + screenHeightSummant where pxPerTickEndBase = 160 if pxPerTickEndSemantic = "large" 120 if pxPerTickEndSemantic = "medium" 80 if pxPerTickEndSemantic = "small" where inertiaFactor = 1 if inertia = "2.3 Xcode Momentum 4" 3/4 if inertia = "3 Snappy" 2/3 if inertia = "4. MMF" where screenHeightSummant = (screenHeightFactor-1)*20 if screenHeightFactor > 1 ((1/screenHeightFactor)-1)*-20 if screenHeightFactor < 1 where screenHeightFactor = actualScreenHeight / 1080

公式的约束设计(文档注明):

screenHeightSummant(screenHeightFactor = 1) = 0 screenHeightSummant(screenHeightFactor = 2) = 20 screenHeightSummant(screenHeightFactor = 1/x) = - screenHeightSummant(screenHeightFactor = x)

作者用 7 组实测验证了该公式(如 2160p + Snappy 2 + large →160*(2/3)+20 = 126,判定"Feels good"),结论是"公式基本可用"。需要说明的是,作者自己对公式的合理性也持保留态度("Not sure if the screenHeightSummant makes sense"),且当前 ScrollConfig.swift 已改用更平滑的加权公式:maxSens = maxSens*(0.9) + maxSens*0.1*screenSizeFactor,基准屏幕为 1920×1080,权重 0.1——这是文档公式的工程化演进。

六、文档末尾的设计决策与待办思考

文档的"Notes"部分记录了三个尚未完全定论的设计决策,值得作为调参时的思考题:

  1. msPerStep 是否应随 pxPerTick 变化?—— 结论:不需要,恒定 msPerStep 手感良好;
  2. pxPerTick 选项是否应随惯性档位变化?—— 结论:应该。惯性越大,大步长(至少 pxPerTickEnd)越合适;
  3. pxPerTick 是否应随屏幕尺寸变化?—— 结论:可能应该,更大的屏幕空间适配更大的步长(这最终演变为第五节中的屏幕自适应公式)。

这三个问题后来在 ScrollConfig.swift 的加速度曲线(getAccelerationCurve)与scaleToDisplay机制中分别得到落实:当scaleToDisplay=true时按屏幕尺寸缩放maxSens,横向以 1920 为基准、纵向以 1080 为基准(ScrollConfig.swift)。

七、延伸阅读与源码导航

若想深入验证本文所述的实现细节,可按以下路径继续阅读:

  • 参数载体与曲线映射:ScrollConfig.swift(animationCurveParamsMap,注释明确指向本测试文档);
  • 动画运行时装配:Scroll.m(baseDuration 计算与 HybridCurve 构造);
  • 惯性曲线数学:DragCurve.swift(系数、指数、停止速度的约束与求解);
  • 混合曲线过渡:HybridCurves.swift(贝塞尔段到惯性段的过渡点搜索);
  • Trackpad 模拟参数实证:GestureScrollSimulator.m(dragCoeff=30, dragExp=0.7与文档 2.2 节互相印证);
  • 用户配置入口:default_config.plist(Scroll.smooth/Scroll.speed/Scroll.trackpadSimulation等用户可见选项)。

结语

ScrollConfigTesting 是一份罕见的"调参现场实录":它记录了 MMF 滚动引擎从"线性平滑"走向"混合惯性曲线"的完整试错轨迹,也沉淀了msPerStep、dragCoefficient、dragExponent、stopSpeed、pxPerTick这一整套可复用的手感参数体系。对照 ScrollConfig.swift 的落地代码可以看到,文档中的每一个甜点参数最终都变成了animationCurveParamsMap中真实运行的曲线——这份文档既是 MMF 滚动手感的设计史,也是一份可迁移到任何"滚轮平滑 + 惯性"实现上的实战调参手册。

  • 桌面应用
  • 系统编程

【免费下载链接】mac-mouse-fix

Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad!

项目地址:https://gitcode.com/GitHub_Trending/ma/mac-mouse-fix
点击查看免费下载

相关推荐

上一篇:Android PDFView:快速实现Android PDF阅读功能的终极指南
下一篇:NATS.go持久化消费者优雅关闭机制完整指南

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

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

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

立即咨询