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:
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useEnv } from '@/context/EnvContext';
|
||||
import { FoliateView } from '@/types/view';
|
||||
import { ViewSettings } from '@/types/book';
|
||||
@@ -9,7 +9,6 @@ import { eventDispatcher } from '@/utils/event';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { tauriGetWindowLogicalPosition } from '@/utils/window';
|
||||
import { getReadingRulerMoveDirection } from '../utils/readingRuler';
|
||||
import { SmoothScroller, type SmoothScrollTarget } from '../utils/smoothWheelScroll';
|
||||
import { useTouchInterceptor } from './useTouchInterceptor';
|
||||
|
||||
export type ScrollSource = 'touch' | 'mouse';
|
||||
@@ -111,16 +110,6 @@ export const usePagination = (
|
||||
const { getViewSettings, getViewState } = useReaderStore();
|
||||
const { hoveredBookKey, setHoveredBookKey } = useReaderStore();
|
||||
const { acquireVolumeKeyInterception, releaseVolumeKeyInterception } = useDeviceControlStore();
|
||||
const smoothScrollerRef = useRef<SmoothScroller | null>(null);
|
||||
const smoothScrollTargetRef = useRef<SmoothScrollTarget>({
|
||||
get position() {
|
||||
return viewRef.current?.renderer.containerPosition ?? 0;
|
||||
},
|
||||
set position(value: number) {
|
||||
const renderer = viewRef.current?.renderer;
|
||||
if (renderer) renderer.containerPosition = value;
|
||||
},
|
||||
});
|
||||
|
||||
const handlePageFlip = async (
|
||||
msg: MessageEvent | CustomEvent | React.MouseEvent<HTMLDivElement, MouseEvent>,
|
||||
@@ -199,37 +188,22 @@ export const usePagination = (
|
||||
viewPagination(viewRef.current, viewSettings, side);
|
||||
}
|
||||
}
|
||||
} else if (msg.data.type === 'iframe-wheel') {
|
||||
const { deltaY, deltaX, isMouseWheel } = msg.data;
|
||||
if (
|
||||
viewSettings.scrolled &&
|
||||
isMouseWheel &&
|
||||
!isPanningView(viewRef.current, viewSettings)
|
||||
) {
|
||||
// Mouse wheels deliver one large quantised delta per notch which
|
||||
// Chromium would scroll without interpolation, producing the
|
||||
// jerky one-step-per-frame motion reported on Windows. The
|
||||
// iframe handler already preventDefault'd the native scroll —
|
||||
// here we replay the delta as a smooth animation instead.
|
||||
if (!smoothScrollerRef.current) {
|
||||
smoothScrollerRef.current = new SmoothScroller();
|
||||
}
|
||||
smoothScrollerRef.current.scrollBy(smoothScrollTargetRef.current, deltaY);
|
||||
} else if (!viewSettings.scrolled && !isPanningView(viewRef.current, viewSettings)) {
|
||||
// Paginated mode: wheel always flips a page (the iframe doesn't
|
||||
// scroll because the container has overflow:hidden).
|
||||
if (deltaY > 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'down');
|
||||
} else if (deltaY < 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'up');
|
||||
} else if (deltaX < 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'left');
|
||||
} else if (deltaX > 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'right');
|
||||
}
|
||||
} else if (
|
||||
msg.data.type === 'iframe-wheel' &&
|
||||
!viewSettings.scrolled &&
|
||||
!isPanningView(viewRef.current, viewSettings)
|
||||
) {
|
||||
// The wheel event is handled by the iframe itself in scrolled mode.
|
||||
const { deltaY, deltaX } = msg.data;
|
||||
if (deltaY > 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'down');
|
||||
} else if (deltaY < 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'up');
|
||||
} else if (deltaX < 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'left');
|
||||
} else if (deltaX > 0) {
|
||||
viewPagination(viewRef.current, viewSettings, 'right');
|
||||
}
|
||||
// Otherwise (scrolled mode + trackpad/high-resolution input) the
|
||||
// browser's native scroll already runs and is pixel-precise.
|
||||
} else if (msg.data.type === 'iframe-mouseup') {
|
||||
if (msg.data.button === 3) {
|
||||
viewRef.current?.history.back();
|
||||
@@ -273,12 +247,6 @@ export const usePagination = (
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
smoothScrollerRef.current?.cancel();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appService?.isMobileApp) return;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user