Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions src/List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,14 +188,6 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
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) {
Expand All @@ -215,10 +207,20 @@ export function RawList<T>(props: ListProps<T>, ref: React.Ref<ListRef>) {
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]);

Expand Down
40 changes: 35 additions & 5 deletions src/hooks/useFrameWheel.ts
Original file line number Diff line number Diff line change
@@ -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<number>(null);

function onWheel(event: MouseWheelEvent) {
// Firefox patch
const wheelValueRef = useRef<number>(null);
const isMouseScrollRef = useRef<boolean>(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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个数字会不会跟着操作系统和硬件发生变化?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

和 FF 内部的 mousewheel.default.delta_multiplier_y 有关,FF 本身不推荐修改它,可以忽略。如果遇到了再调整:
截屏2020-08-25 下午4 11 21

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];
}
2 changes: 2 additions & 0 deletions src/utils/isFirefox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const isFF = /Firefox/i.test(navigator.userAgent);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to fix for serveer-side-rendering

const isFF = typeof navigator !== 'undefined' && /Firefox/i.test(navigator.userAgent);

Current nodejs error:

ReferenceError: navigator is not defined
vpc-admin: [dev]     at Object.<anonymous> (/Users/nodkz/www/ps/vpc/apps/admin/node_modules/rc-virtual-list/lib/utils/isFirefox.js:7:28)

export default isFF;
82 changes: 82 additions & 0 deletions tests/scroll-Firefox.test.js
Original file line number Diff line number Diff line change
@@ -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 = (
<List component="ul" itemKey="id" {...props}>
{({ id }) => <li>{id}</li>}
</List>
);

if (props.ref) {
node = <div>{node}</div>;
}

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();
});
});