Fix mouse cursor drift during drag scroll
Previously, the drag-scroll implementation used incremental relative positioning which accumulated rounding errors over time. This caused the mouse cursor to gradually drift away from its original position during prolonged drag operations, particularly noticeable on slower hardware or systems with lower frame rates. The fix switches from cumulative delta updates to absolute positioning: - Track initial mouse and scroll positions when drag starts - Calculate scroll position as: initialScroll - (currentMouse - initialMouse) - This ensures the visual position remains locked throughout the entire drag This change eliminates drift by avoiding cumulative floating-point errors that occurred with the previous += approach. 1. Inverted Logic: The code adds the mouse delta to scrollLeft/scrollTop, but this is backwards. When you drag right, you want the content to move left (scroll increases), and vice versa. 2. Cumulative Rounding Errors: Using += with floating-point deltas accumulates rounding errors over time, especially on slower hardware where mousemove events may be processed less frequently or with more delay. 3. Frame Rate Dependency: On slower hardware, if the browser can't keep up with all mouse events, some movements get skipped, but the positions still update, causing the reference point to drift.
This commit is contained in:
@@ -20,7 +20,6 @@ type Positions = [OldestPos: number, SecondOldestPos: number, LatestPos: number]
|
||||
type ClickTimes = [OldestTime: number, SecondOldestTime: number, LatestTime: number];
|
||||
|
||||
const OLDEST = 2;
|
||||
const SECOND_OLDEST = 1;
|
||||
const LATEST = 0;
|
||||
|
||||
const X = 0;
|
||||
@@ -38,6 +37,8 @@ export const useMouseDragScroll = (
|
||||
const previousClickTime = useRef<ClickTimes>([0, 0, 0]);
|
||||
const scrollAtT0 = useRef<[ScrollLeft: number, ScrollTop: number]>([0, 0]);
|
||||
const inertiaTimeInterval = useRef<number>(undefined);
|
||||
const initialMousePos = useRef<[x: number, y: number]>([0, 0]);
|
||||
const initialScrollPos = useRef<[left: number, top: number]>([0, 0]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref?.current;
|
||||
@@ -179,12 +180,13 @@ export const useMouseDragScroll = (
|
||||
previousClickPosY.current = [...(previousClickPosY.current.slice(1) as [number, number]), e.pageY];
|
||||
previousClickTime.current = [...(previousClickTime.current.slice(1) as [number, number]), Date.now()];
|
||||
|
||||
// Use absolute positioning from initial drag position to prevent drift
|
||||
if (handleScrollX) {
|
||||
element.scrollLeft += previousClickPosX.current[LATEST] - previousClickPosX.current[SECOND_OLDEST];
|
||||
element.scrollLeft = initialScrollPos.current[0] - (e.pageX - initialMousePos.current[0]);
|
||||
}
|
||||
|
||||
if (handleScrollY) {
|
||||
element.scrollTop += previousClickPosY.current[LATEST] - previousClickPosY.current[SECOND_OLDEST];
|
||||
element.scrollTop = initialScrollPos.current[1] - (e.pageY - initialMousePos.current[1]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -215,6 +217,10 @@ export const useMouseDragScroll = (
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Store initial positions for absolute positioning during drag
|
||||
initialMousePos.current = [e.pageX, e.pageY];
|
||||
initialScrollPos.current = [element.scrollLeft, element.scrollTop];
|
||||
|
||||
previousClickPosX.current = [e.pageX, e.pageX, e.pageX];
|
||||
previousClickPosY.current = [e.pageY, e.pageY, e.pageY];
|
||||
previousClickTime.current = [Date.now() - 2, Date.now() - 1, Date.now()];
|
||||
|
||||
Reference in New Issue
Block a user