- 桌面应用
- 系统编程
【免费下载链接】mac-mouse-fix
Mac Mouse Fix - Make Your $10 Mouse Better Than an Apple Trackpad!
导读
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 的滚动动画由三类元素构成,理解它们是读懂本文所有参数的前提:
- BaseCurve(贝塞尔基础曲线):控制"页面跟随手指推动"阶段的运动形态,参数为四个控制点,如
(0,0), (0,0), (1,1), (1,1)即线性曲线; - DragCurve(拖拽/惯性曲线):控制松手后"惯性滑行"阶段的减速过程,由
dragCoefficient、dragExponent、stopSpeed三个参数描述; - 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"
| 参数 | 值 | 说明 |
|---|---|---|
| pxPerTickBase | 60 | 单 tick 基础步长 |
| pxPerTickEnd | 90 | 高速目标步长 |
| BaseCurve | (0,0),(0,0),(1,1),(1,1) | 线性曲线 |
| msPerStep | 140 | 基础动画时长 |
| dragCoefficient | 23 | 略低于 MMF 2 的 25,减少僵硬感 |
| dragExponent | 1.0 | 线性衰减 |
| stopSpeed | 50 | 快速停止,防止爬行 |
文档备注:这组参数"非常贴近 MMF 2 的手感但少一点僵硬"。在 ScrollConfig.swift 中可看到该曲线的实际落地(baseMsPerStepCurve版本)。
3.2 Mid Inertia → 使用 "3. Snappy"
| 参数 | 值 | 说明 |
|---|---|---|
| pxPerTickBase | 60 | |
| pxPerTickEnd | 160 | 高速步长大 |
| BaseCurve | (0,0),(0,0),(1,1),(1,1) | 线性 |
| msPerStep | 180 | |
| dragCoefficient | 10 | 衰减轻,滑行远 |
| dragExponent | 1.1 | 略高于 1,快速衰减 |
| stopSpeed | 50 |
这组是"响应与滑行"的折中点,最终映射为kMFScrollAnimationCurveNameMediumInertia(虽然当前版本中该枚举分支为fatalError(),说明已被后续曲线替代,见 ScrollConfig.swift)。
3.3 High Inertia → 使用 "2.3 Xcode Momentum 4"
| 参数 | 值 | 说明 |
|---|---|---|
| pxPerTickBase | 60 | |
| pxPerTickEnd | 160 | |
| BaseCurve | (0,0),(0,0),(1,1),(1,1) | 线性 |
| msPerStep | 205 | 让单 tick 更顺滑 |
| dragCoefficient | 40 | 强衰减,滑行短 |
| dragExponent | 0.7 | 减速曲线(Trackpad 风格) |
| stopSpeed | 50 |
关键提示:文档指出,只要在 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):用极端值"关闭"惯性,文档甚至坦言"当前反而更喜欢这个"。
四、文档中的调参方法论:从"手感对照"到"甜点收敛"
这份文档最有价值的其实是调参方法。其流程可以概括为:
- 设定基准曲线:所有候选先用同一线性 BaseCurve 起跑,隔离其他变量;
- 甜点枚举:固定大部分参数,只扫一个维度(如 msPerStep 依次取 110/120/140/180/205/220),记录每个值的手感关键词("floaty"、"abrupt"、"responsive"、"stiff");
- 交叉对比(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 的响应。
- 候选 1(
- 以版本为标尺:文档频繁以"MMF 2 手感""MMF 0.9 手感"作为参照物校准,并把 MMF 3 的 step size 与 MMF 2 默认档对齐,用于解释为何更高的平滑度是合适的。
- 记录反直觉现象:例如"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"部分记录了三个尚未完全定论的设计决策,值得作为调参时的思考题:
- msPerStep 是否应随 pxPerTick 变化?—— 结论:不需要,恒定 msPerStep 手感良好;
- pxPerTick 选项是否应随惯性档位变化?—— 结论:应该。惯性越大,大步长(至少 pxPerTickEnd)越合适;
- 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!
相关推荐
终极实战:用Gemini Lyria RealTime在3分钟内构建你的AI音乐创作系统
终极实战:用Gemini Lyria RealTime在3分钟内构建你的AI音乐创作系统 你是否曾梦想过与AI进行实时音乐对话,让创意灵感瞬间转化为动听旋律?G
示例工程人工智能大模型Browser-Use终极指南:让AI像人类一样浏览网页的完整解决方案
Browser Use终极指南:让AI像人类一样浏览网页的完整解决方案 Browser Use是一款革命性的AI网页自动化工具,它能让你用自然语言指挥AI完成复
人工智能AI Agent浏览器控制GUI 自动化MCP 服务Mac Mouse Fix与系统字体平滑设置:调整文字抗锯齿
Mac Mouse Fix与系统字体平滑设置:调整文字抗锯齿 引言:字体平滑的重要性与常见问题 你是否曾在Mac上遇到过文字显示模糊、边缘锯齿明显的问题?尤其在
桌面应用系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考