From d8a8b900ab2e17e6e81e25746ffba1aea757dc34 Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 25 Aug 2020 14:49:25 +0800 Subject: [PATCH 1/2] fix: Add patch fix for firefox mouse wheel --- src/List.tsx | 18 +++++++++-------- src/hooks/useFrameWheel.ts | 40 +++++++++++++++++++++++++++++++++----- src/utils/isFirefox.ts | 2 ++ 3 files changed, 47 insertions(+), 13 deletions(-) create mode 100644 src/utils/isFirefox.ts diff --git a/src/List.tsx b/src/List.tsx index 0dbd6f1a..a6aede77 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -188,14 +188,6 @@ export function RawList(props: ListProps, ref: React.Ref) { const keepInRange = useInRange(scrollHeight, height); // ================================ Scroll ================================ - // Since this added in global,should use ref to keep update - const onRawWheel = useFrameWheel(inVirtual, offsetY => { - syncScrollTop(top => { - const newTop = keepInRange(top + offsetY); - return newTop; - }); - }); - function onScrollBar(newScrollTop: number) { const newTop = keepInRange(newScrollTop); if (newTop !== scrollTop) { @@ -215,10 +207,20 @@ export function RawList(props: ListProps, ref: React.Ref) { onScroll?.(e); } + // Since this added in global,should use ref to keep update + const [onRawWheel, onFireFoxScroll] = useFrameWheel(inVirtual, offsetY => { + syncScrollTop(top => { + const newTop = keepInRange(top + offsetY); + return newTop; + }); + }); + React.useEffect(() => { componentRef.current.addEventListener('wheel', onRawWheel); + componentRef.current.addEventListener('DOMMouseScroll', onFireFoxScroll as any); return () => { componentRef.current.removeEventListener('wheel', onRawWheel); + componentRef.current.removeEventListener('DOMMouseScroll', onFireFoxScroll as any); }; }, [inVirtual]); diff --git a/src/hooks/useFrameWheel.ts b/src/hooks/useFrameWheel.ts index 110c112a..53d41639 100644 --- a/src/hooks/useFrameWheel.ts +++ b/src/hooks/useFrameWheel.ts @@ -1,24 +1,54 @@ import { useRef } from 'react'; import raf from 'rc-util/lib/raf'; +import isFF from '../utils/isFirefox'; -export default function useFrameWheel(inVirtual: boolean, onWheelDelta: (offset: number) => void) { +interface FireFoxDOMMouseScrollEvent { + detail: number; + preventDefault: Function; +} + +export default function useFrameWheel( + inVirtual: boolean, + onWheelDelta: (offset: number) => void, +): [(e: WheelEvent) => void, (e: FireFoxDOMMouseScrollEvent) => void] { const offsetRef = useRef(0); const nextFrameRef = useRef(null); - function onWheel(event: MouseWheelEvent) { + // Firefox patch + const wheelValueRef = useRef(null); + const isMouseScrollRef = useRef(false); + + function onWheel(event: WheelEvent) { if (!inVirtual) return; // Proxy of scroll events - event.preventDefault(); + if (!isFF) { + event.preventDefault(); + } raf.cancel(nextFrameRef.current); + offsetRef.current += event.deltaY; + wheelValueRef.current = event.deltaY; nextFrameRef.current = raf(() => { - onWheelDelta(offsetRef.current); + // Patch a multiple for Firefox to fix wheel number too small + // ref: https://github.com/ant-design/ant-design/issues/26372#issuecomment-679460266 + const patchMultiple = isMouseScrollRef.current ? 10 : 1; + onWheelDelta(offsetRef.current * patchMultiple); offsetRef.current = 0; }); } - return onWheel; + // A patch for firefox + function onFireFoxScroll(event: FireFoxDOMMouseScrollEvent) { + if (!inVirtual) return; + + // Firefox level stop + event.preventDefault(); + + isMouseScrollRef.current = event.detail === wheelValueRef.current; + } + + return [onWheel, onFireFoxScroll]; } diff --git a/src/utils/isFirefox.ts b/src/utils/isFirefox.ts new file mode 100644 index 00000000..f4271cec --- /dev/null +++ b/src/utils/isFirefox.ts @@ -0,0 +1,2 @@ +const isFF = /Firefox/i.test(navigator.userAgent); +export default isFF; From 8ae481ac1a723173200787f3baae035e40965198 Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 25 Aug 2020 15:05:26 +0800 Subject: [PATCH 2/2] add test case --- tests/scroll-Firefox.test.js | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/scroll-Firefox.test.js diff --git a/tests/scroll-Firefox.test.js b/tests/scroll-Firefox.test.js new file mode 100644 index 00000000..ce50aab4 --- /dev/null +++ b/tests/scroll-Firefox.test.js @@ -0,0 +1,82 @@ +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mount } from 'enzyme'; +import { spyElementPrototypes } from './utils/domHook'; +import List from '../src'; +import isFF from '../src/utils/isFirefox'; + +function genData(count) { + return new Array(count).fill(null).map((_, index) => ({ id: String(index) })); +} + +jest.mock('../src/utils/isFirefox', () => true); + +describe('List.Firefox-Scroll', () => { + let mockElement; + + beforeAll(() => { + mockElement = spyElementPrototypes(HTMLElement, { + offsetHeight: { + get: () => 20, + }, + clientHeight: { + get: () => 100, + }, + }); + }); + + afterAll(() => { + mockElement.mockRestore(); + }); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + function genList(props) { + let node = ( + + {({ id }) =>
  • {id}
  • } +
    + ); + + if (props.ref) { + node =
    {node}
    ; + } + + return mount(node); + } + + it('should be true', () => { + expect(isFF).toBe(true); + }); + + // https://github.com/ant-design/ant-design/issues/26372 + it('FireFox should patch scroll speed', () => { + const wheelPreventDefault = jest.fn(); + const firefoxPreventDefault = jest.fn(); + const wrapper = genList({ itemHeight: 20, height: 100, data: genData(100) }); + const ulElement = wrapper.find('ul').instance(); + + act(() => { + const wheelEvent = new Event('wheel'); + wheelEvent.deltaY = 3; + wheelEvent.preventDefault = wheelPreventDefault; + ulElement.dispatchEvent(wheelEvent); + + const firefoxScrollEvent = new Event('DOMMouseScroll'); + firefoxScrollEvent.detail = 3; + firefoxScrollEvent.preventDefault = firefoxPreventDefault; + ulElement.dispatchEvent(firefoxScrollEvent); + + jest.runAllTimers(); + }); + + expect(wheelPreventDefault).not.toHaveBeenCalled(); + expect(firefoxPreventDefault).toHaveBeenCalled(); + }); +});