Skip to content

Commit 5868742

Browse files
authored
fix: Add patch fix for firefox mouse wheel (#49)
* fix: Add patch fix for firefox mouse wheel * add test case
1 parent 8108b1d commit 5868742

File tree

4 files changed

+129
-13
lines changed

4 files changed

+129
-13
lines changed

src/List.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,6 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
188188
const keepInRange = useInRange(scrollHeight, height);
189189

190190
// ================================ Scroll ================================
191-
// Since this added in global,should use ref to keep update
192-
const onRawWheel = useFrameWheel(inVirtual, offsetY => {
193-
syncScrollTop(top => {
194-
const newTop = keepInRange(top + offsetY);
195-
return newTop;
196-
});
197-
});
198-
199191
function onScrollBar(newScrollTop: number) {
200192
const newTop = keepInRange(newScrollTop);
201193
if (newTop !== scrollTop) {
@@ -215,10 +207,20 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
215207
onScroll?.(e);
216208
}
217209

210+
// Since this added in global,should use ref to keep update
211+
const [onRawWheel, onFireFoxScroll] = useFrameWheel(inVirtual, offsetY => {
212+
syncScrollTop(top => {
213+
const newTop = keepInRange(top + offsetY);
214+
return newTop;
215+
});
216+
});
217+
218218
React.useEffect(() => {
219219
componentRef.current.addEventListener('wheel', onRawWheel);
220+
componentRef.current.addEventListener('DOMMouseScroll', onFireFoxScroll as any);
220221
return () => {
221222
componentRef.current.removeEventListener('wheel', onRawWheel);
223+
componentRef.current.removeEventListener('DOMMouseScroll', onFireFoxScroll as any);
222224
};
223225
}, [inVirtual]);
224226

src/hooks/useFrameWheel.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,54 @@
11
import { useRef } from 'react';
22
import raf from 'rc-util/lib/raf';
3+
import isFF from '../utils/isFirefox';
34

4-
export default function useFrameWheel(inVirtual: boolean, onWheelDelta: (offset: number) => void) {
5+
interface FireFoxDOMMouseScrollEvent {
6+
detail: number;
7+
preventDefault: Function;
8+
}
9+
10+
export default function useFrameWheel(
11+
inVirtual: boolean,
12+
onWheelDelta: (offset: number) => void,
13+
): [(e: WheelEvent) => void, (e: FireFoxDOMMouseScrollEvent) => void] {
514
const offsetRef = useRef(0);
615
const nextFrameRef = useRef<number>(null);
716

8-
function onWheel(event: MouseWheelEvent) {
17+
// Firefox patch
18+
const wheelValueRef = useRef<number>(null);
19+
const isMouseScrollRef = useRef<boolean>(false);
20+
21+
function onWheel(event: WheelEvent) {
922
if (!inVirtual) return;
1023

1124
// Proxy of scroll events
12-
event.preventDefault();
25+
if (!isFF) {
26+
event.preventDefault();
27+
}
1328

1429
raf.cancel(nextFrameRef.current);
30+
1531
offsetRef.current += event.deltaY;
32+
wheelValueRef.current = event.deltaY;
1633

1734
nextFrameRef.current = raf(() => {
18-
onWheelDelta(offsetRef.current);
35+
// Patch a multiple for Firefox to fix wheel number too small
36+
// ref: https://github.com/ant-design/ant-design/issues/26372#issuecomment-679460266
37+
const patchMultiple = isMouseScrollRef.current ? 10 : 1;
38+
onWheelDelta(offsetRef.current * patchMultiple);
1939
offsetRef.current = 0;
2040
});
2141
}
2242

23-
return onWheel;
43+
// A patch for firefox
44+
function onFireFoxScroll(event: FireFoxDOMMouseScrollEvent) {
45+
if (!inVirtual) return;
46+
47+
// Firefox level stop
48+
event.preventDefault();
49+
50+
isMouseScrollRef.current = event.detail === wheelValueRef.current;
51+
}
52+
53+
return [onWheel, onFireFoxScroll];
2454
}

src/utils/isFirefox.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const isFF = /Firefox/i.test(navigator.userAgent);
2+
export default isFF;

tests/scroll-Firefox.test.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React from 'react';
2+
import { act } from 'react-dom/test-utils';
3+
import { mount } from 'enzyme';
4+
import { spyElementPrototypes } from './utils/domHook';
5+
import List from '../src';
6+
import isFF from '../src/utils/isFirefox';
7+
8+
function genData(count) {
9+
return new Array(count).fill(null).map((_, index) => ({ id: String(index) }));
10+
}
11+
12+
jest.mock('../src/utils/isFirefox', () => true);
13+
14+
describe('List.Firefox-Scroll', () => {
15+
let mockElement;
16+
17+
beforeAll(() => {
18+
mockElement = spyElementPrototypes(HTMLElement, {
19+
offsetHeight: {
20+
get: () => 20,
21+
},
22+
clientHeight: {
23+
get: () => 100,
24+
},
25+
});
26+
});
27+
28+
afterAll(() => {
29+
mockElement.mockRestore();
30+
});
31+
32+
beforeEach(() => {
33+
jest.useFakeTimers();
34+
});
35+
36+
afterEach(() => {
37+
jest.useRealTimers();
38+
});
39+
40+
function genList(props) {
41+
let node = (
42+
<List component="ul" itemKey="id" {...props}>
43+
{({ id }) => <li>{id}</li>}
44+
</List>
45+
);
46+
47+
if (props.ref) {
48+
node = <div>{node}</div>;
49+
}
50+
51+
return mount(node);
52+
}
53+
54+
it('should be true', () => {
55+
expect(isFF).toBe(true);
56+
});
57+
58+
// https://github.com/ant-design/ant-design/issues/26372
59+
it('FireFox should patch scroll speed', () => {
60+
const wheelPreventDefault = jest.fn();
61+
const firefoxPreventDefault = jest.fn();
62+
const wrapper = genList({ itemHeight: 20, height: 100, data: genData(100) });
63+
const ulElement = wrapper.find('ul').instance();
64+
65+
act(() => {
66+
const wheelEvent = new Event('wheel');
67+
wheelEvent.deltaY = 3;
68+
wheelEvent.preventDefault = wheelPreventDefault;
69+
ulElement.dispatchEvent(wheelEvent);
70+
71+
const firefoxScrollEvent = new Event('DOMMouseScroll');
72+
firefoxScrollEvent.detail = 3;
73+
firefoxScrollEvent.preventDefault = firefoxPreventDefault;
74+
ulElement.dispatchEvent(firefoxScrollEvent);
75+
76+
jest.runAllTimers();
77+
});
78+
79+
expect(wheelPreventDefault).not.toHaveBeenCalled();
80+
expect(firefoxPreventDefault).toHaveBeenCalled();
81+
});
82+
});

0 commit comments

Comments
 (0)