跳到主要内容

scroll end event plugin

一、作用

二、注册事件

备注
function registerEvents() {
registerTwoPhaseEvent('onScrollEnd', [
'scroll',
'scrollend',
'touchstart',
'touchcancel',
'touchend',
'mousedown',
'mouseup',
]);
}

三、提取事件

备注
/**
* This plugin creates an `onScrollEnd` event polyfill when the native one
* is not available.
* 当本地 `onScrollEnd` 事件不可用时,该插件会创建一个 `onScrollEnd` 事件的 `polyfill`
*/
function extractEvents(
dispatchQueue: DispatchQueue,
domEventName: DOMEventName,
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: null | EventTarget,
eventSystemFlags: EventSystemFlags,
targetContainer: null | EventTarget,
) {
if (!enableScrollEndPolyfill) {
return;
}

const inCapturePhase = (eventSystemFlags & IS_CAPTURE_PHASE) !== 0;

if (domEventName !== 'scrollend') {
if (!isScrollEndEventSupported && inCapturePhase) {
switch (domEventName) {
case 'scroll': {
if (nativeEventTarget !== null) {
debounceScrollEnd(targetInst, nativeEvent, nativeEventTarget);
}
break;
}
case 'touchstart': {
isTouchStarted = true;
break;
}
case 'touchcancel':
case 'touchend': {
// Note we cannot use pointer events for this because they get
// cancelled when native scrolling takes control.
// 注意我们不能为此使用指针事件,因为当原生滚动接管控制时,它们会被取消
isTouchStarted = false;
break;
}
case 'mousedown': {
isMouseDown = true;
break;
}
case 'mouseup': {
isMouseDown = false;
break;
}
}
}
return;
}

if (!isScrollEndEventSupported && nativeEventTarget !== null) {
const existingTimer = getScrollEndTimer(nativeEventTarget);
if (existingTimer != null) {
// If we do get a native scrollend event fired, we cancel the polyfill.
// This could happen if our feature detection is broken or if there's another
// polyfill calling dispatchEvent to fire it before we fire ours.
// 如果我们确实触发了原生的 `scrollend` 事件,我们就取消这个 `polyfill` 。如果我们的特性检测出错,或者如果有另一个
// `polyfill` 在我们触发之前调用 `dispatchEvent` 来触发它,这种情况可能会发生。
clearTimeout(existingTimer);
clearScrollEndTimer(nativeEventTarget);
} else {
// If we didn't receive a 'scroll' event first, we ignore this event to avoid
// double firing. Such as if we fired our onScrollEnd polyfill and then
// we also observed a native one afterwards.
// 如果我们没有先收到 `scroll` 事件,我们将忽略此事件以避免事件的双重触发。例如,如果我们触发了 `onScrollEnd` 的
// `polyfill` , 然后之后又观察到了原生事件。
return;
}
}

// In React onScrollEnd doesn't bubble.
// 在 React 中, `onScrollEnd` 不会冒泡。
const accumulateTargetOnly = !inCapturePhase;

const listeners = accumulateSinglePhaseListeners(
targetInst,
'onScrollEnd',
'scrollend',
inCapturePhase,
accumulateTargetOnly,
nativeEvent,
);

if (listeners.length > 0) {
// Intentionally create event lazily.
// 有意延迟创建事件。
const event: ReactSyntheticEvent = new SyntheticUIEvent(
'onScrollEnd',
'scrollend',
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({ event, listeners });
}
}

四、常量

1. 是否支持滚动结束事件

备注

源码中 39 -40 行

const isScrollEndEventSupported =
enableScrollEndPolyfill && canUseDOM && isEventSupported('scrollend');

2. 消抖超时

备注

源码中 96 - 99 行

// When scrolling slows down the frequency of new scroll events can be quite low.
// This timeout seems high enough to cover those cases but short enough to not
// fire the event way too late.
// 当滚动变慢时,新的滚动事件的频率可能相当低。
// 这个超时时间似乎足够覆盖这些情况,但又足够短,不会太晚触发事件。

// 消抖超时
const DEBOUNCE_TIMEOUT = 200;

五、变量

1. 触摸已开始

// 触摸已开始
let isTouchStarted = false;
// 鼠标是否按下
let isMouseDown = false;

六、工具

1. 手动派发滚动结束事件

备注
function manualDispatchScrollEndEvent(
inst: Fiber,
nativeEvent: AnyNativeEvent,
target: EventTarget,
) {
const dispatchQueue: DispatchQueue = [];
const listeners = accumulateTwoPhaseListeners(inst, 'onScrollEnd');
if (listeners.length > 0) {
const event: ReactSyntheticEvent = new SyntheticUIEvent(
'onScrollEnd',
'scrollend',
null,
// 这将是“滚动”事件。
nativeEvent, // This will be the "scroll" event.
target,
);
dispatchQueue.push({ event, listeners });
}
batchedUpdates(runEventInBatch, dispatchQueue);
}

2. 批量运行事件

备注
function runEventInBatch(dispatchQueue: DispatchQueue) {
processDispatchQueue(dispatchQueue, 0);
}

3. 触发滚动结束

备注
function fireScrollEnd(
targetInst: Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
): void {
clearScrollEndTimer(nativeEventTarget);
if (isMouseDown || isTouchStarted) {
// If mouse or touch is down, try again later in case this is due to having an
// active scroll but it's not currently moving.
// 如果鼠标或触摸按下,稍后再试,以防这是由于有活动的滚动但当前未移动
debounceScrollEnd(targetInst, nativeEvent, nativeEventTarget);
return;
}
manualDispatchScrollEndEvent(targetInst, nativeEvent, nativeEventTarget);
}

4. 防抖滚动结束

备注
function debounceScrollEnd(
targetInst: null | Fiber,
nativeEvent: AnyNativeEvent,
nativeEventTarget: EventTarget,
) {
const existingTimer = getScrollEndTimer(nativeEventTarget);
if (existingTimer != null) {
clearTimeout(existingTimer);
}
if (targetInst !== null) {
const newTimer = setTimeout(
fireScrollEnd.bind(null, targetInst, nativeEvent, nativeEventTarget),
DEBOUNCE_TIMEOUT,
);
setScrollEndTimer(nativeEventTarget, newTimer);
}
}