Mouse-wheel scrolling feels jerky on macOS
macOS hands a mouse wheel to applications as a sequence of discrete clicks, while a trackpad gets continuous gestures. Making the wheel feel smooth means intercepting those clicks and re-emitting them frame by frame — and that is where three real traps live.
What macOS actually does with a wheel
A trackpad reports a continuous gesture — the system receives a stream of positions and knows the finger is still moving. A mouse wheel reports discrete clicks. One notch is one event, with a line count attached, and macOS hands it to the app and forgets about it.
So a wheel does not "scroll badly" because something is broken. It jumps because that is what it was asked to do: one notch, one jump. Applications that smooth wheel input do it themselves; most do not.
The mechanism that actually fixes it
There are three steps, and all three matter:
- Intercept before the app sees it. A
CGEventTapreceives the wheel event first, so it can be swallowed and replaced. - Accumulate, do not consume. The engine keeps the unspent remainder of the notch and hands out a fraction of it per frame.
- Emit on the display clock. A
CVDisplayLinkfires in step with the panel refresh, so the deltas arrive with the frames rather than in a burst.
This is the architecture the open-source MOS popularised, and it is the right one — our scroll engine mirrors it rather than inventing a worse wheel.
Trap 1 — not every wheel notch means "scroll"
This is the one that bites. In Photos, Maps, Keynote, Numbers, and most spreadsheets, one wheel notch is not a scroll at all — it is a zoom step or a page turn.
Smoothed, that step keeps arriving long after you let go. A photo jumps from 80% to 200%. A slide show skips a dozen pages. Users then work around it with per-app whitelists — which is exactly the manual labour a feature like this is supposed to remove.
So before reshaping an event, the engine asks one question: scroll or zoom? Zoom-semantic events are passed straight through, untouched. The same goes for applications whose wheel is already line-precise — shaping them again makes them worse, not better.
Trap 2 — the display link that quietly becomes a zombie
CVDisplayLink does not always survive the night. After a display wake, or a reconfiguration (a cable, a KVM, a resolution change), it can stop delivering callbacks while still reporting that it is running.
If nothing checks, the visible symptom is scroll that simply stops animating — and, from the outside, an app that "randomly broke". The fix is unglamorous: assume the link can die, detect it, and rebuild it.
Trap 3 — two tools fighting over the same wheel
Two event taps at the same level both consume the wheel. Each engine sees the other’s swallowed events as missing input and its own re-emitted events as new input. The two interpolators then chase each other, and the result is scrolling that stutters, doubles, or stops outright.
Users reasonably conclude that the newest tool broke their scrolling. Naming the conflict — "another scroll utility is running" — is far more useful than leaving someone to debug it blind.
If scrolling is bad right now, check these first
- Is a second smoothing tool running? Two at once is worse than none.
- Does the app in question use precise, line-based deltas? Reshaping those adds latency for nothing.
- Did it start after a sleep, wake, or cable change? That points at the display link, not at the app.
- Is it one app or all of them? One app usually means a semantic mismatch (trap 1); all of them points at the tap itself.
Honest limits
Smoothing is reshaping input, not creating it. It adds a small amount of latency by construction, and it cannot make an app that ignores point-precise deltas behave. Where a wheel is already behaving correctly, the right answer is to leave it alone — which is why the semantic and precise-input checks exist at all.
Related
Dimming on a schedule walks the screen to black · How do I change the brightness of an external display? · Mouse acceleration will not stay off · All guides · DDC/CI not responding · Brightness resets after sleep · Text looks blurry · Colours look washed out · Which app should you use?