fix(reader): revert smooth mouse-wheel scrolling in scroll mode, closes #4130 (#4172)

The smooth-wheel feature (#3974, closing #3966) intercepts mouse-wheel
events in scroll mode: it makes the wheel listener non-passive,
preventDefault()s the native scroll, and replays the delta through a
main-thread rAF animation against the renderer container.

That regressed normal mouse scrolling on Windows (#4130): fast wheel
bursts were discarded entirely, and the JS replay is structurally worse
than native scrolling -- a non-passive wheel listener forces every wheel
event (mouse and trackpad) off the compositor thread, and the
postMessage hop plus main-thread animation add latency and jank that
native compositor scrolling does not have.

High-resolution scrolling (e.g. Logitech MX Master, the mouse in #3966)
needs no special API: the OS/driver just delivers regular wheel events
with smaller, more frequent deltas, and the browser scrolls them
natively. #3966's own report ("smooth scrolling works with all
applications apart from yours") points at the interception, not a
missing capability. Restore native wheel scrolling in scroll mode.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Huang Xin
2026-05-15 21:28:57 +08:00
committed by GitHub
parent 4cd5d56b49
commit f5e729a174
5 changed files with 17 additions and 313 deletions
@@ -1,6 +1,5 @@
import { DOUBLE_CLICK_INTERVAL_THRESHOLD_MS, LONG_HOLD_THRESHOLD } from '@/services/constants';
import { eventDispatcher } from '@/utils/event';
import { isLikelyMouseWheel } from './smoothWheelScroll';
let lastClickTime = 0;
let longHoldTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -132,13 +131,6 @@ export const handleMouseup = (bookKey: string, event: MouseEvent) => {
};
export const handleWheel = (bookKey: string, event: WheelEvent) => {
const isMouseWheel = isLikelyMouseWheel(event);
// Suppress the browser's native wheel scroll only for mouse-wheel-shaped
// events. Trackpad / high-resolution input is already pixel-precise, so
// we let it through to keep the existing momentum and 2-axis behaviour.
if (isMouseWheel) {
event.preventDefault();
}
window.postMessage(
{
type: 'iframe-wheel',
@@ -147,7 +139,6 @@ export const handleWheel = (bookKey: string, event: WheelEvent) => {
deltaX: event.deltaX,
deltaY: event.deltaY,
deltaZ: event.deltaZ,
isMouseWheel,
screenX: event.screenX,
screenY: event.screenY,
clientX: event.clientX,