From 88497c08683c78079d131ba0beae25f26b12e66e Mon Sep 17 00:00:00 2001 From: Ye-YiChen <80522109+Ye-YiChen@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:20:02 +0800 Subject: [PATCH] fix: stop smooth touch scrolling once a step is under one pixel A touch swipe that scrolls a virtual list upwards never comes to rest. After the finger lifts, the list keeps moving at a constant 1px per frame until something else interrupts it, instead of easing out the way the opposite direction does. The smooth-scroll interval decays the offset by SMOOTH_PTG and stops once there is nothing left to move: const offset = Math.floor(isHorizontal ? offsetX : offsetY); if (!callback(...) || Math.abs(offset) <= 0.1) clearInterval(...); Math.floor never returns 0 for a negative value - Math.floor(-0.9) is -1. Once the offset decays into (-1, 0), `offset` stays -1 forever, so the stop check never passes and the interval keeps firing. The other direction is unaffected because Math.floor(0.9) is 0, which is why only one direction ever hangs. Use Math.trunc, which rounds towards zero, so the existing `Math.abs(offset) <= 0.1` check finally does what it was written to do - in both directions. The original threshold and structure are kept. Needs all of: a virtual list (height + itemHeight), touch input, and not already being at the top - at the top `useOriginScroll` hands the gesture to the browser and no interval is created at all. Fixes #275 --- src/hooks/useMobileTouchMove.ts | 2 +- tests/touch.test.js | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/hooks/useMobileTouchMove.ts b/src/hooks/useMobileTouchMove.ts index cd3dc36..35ec3d8 100644 --- a/src/hooks/useMobileTouchMove.ts +++ b/src/hooks/useMobileTouchMove.ts @@ -54,7 +54,7 @@ export default function useMobileTouchMove( } else { offsetY *= SMOOTH_PTG; } - const offset = Math.floor(isHorizontal ? offsetX : offsetY); + const offset = Math.trunc(isHorizontal ? offsetX : offsetY); if (!callback(isHorizontal, offset, true) || Math.abs(offset) <= 0.1) { clearInterval(intervalRef.current); } diff --git a/tests/touch.test.js b/tests/touch.test.js index d0a5072..ae53fb6 100644 --- a/tests/touch.test.js +++ b/tests/touch.test.js @@ -1,6 +1,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; import List from '../src'; +import useMobileTouchMove from '../src/hooks/useMobileTouchMove'; import { spyElementPrototypes } from './utils/domHook'; // Mock ScrollBar @@ -188,4 +189,100 @@ describe('List.Touch', () => { '0', ); }); + + describe('smooth scroll ease out', () => { + function setupProbe(impl = () => true) { + const callback = jest.fn(impl); + + function Probe() { + const listRef = React.useRef(null); + useMobileTouchMove(true, listRef, callback); + return ( +