UNPKG

4.08 kBPlain TextView Raw
1'use strict';
2import { useEffect, useRef, useCallback } from 'react';
3import type { SharedValue } from '../commonTypes';
4import type { EventHandlerInternal } from './useEvent';
5import { useEvent } from './useEvent';
6import { useSharedValue } from './useSharedValue';
7import type { AnimatedScrollView } from '../component/ScrollView';
8import type {
9 AnimatedRef,
10 RNNativeScrollEvent,
11 ReanimatedScrollEvent,
12} from './commonTypes';
13import { isWeb } from '../PlatformChecker';
14
15const IS_WEB = isWeb();
16
17/**
18 * Lets you synchronously get the current offset of a `ScrollView`.
19 *
20 * @param animatedRef - An [animated ref](https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedRef) attached to an Animated.ScrollView component.
21 * @returns A shared value which holds the current offset of the `ScrollView`.
22 * @see https://docs.swmansion.com/react-native-reanimated/docs/scroll/useScrollViewOffset
23 */
24export const useScrollViewOffset = IS_WEB
25 ? useScrollViewOffsetWeb
26 : useScrollViewOffsetNative;
27
28function useScrollViewOffsetWeb(
29 animatedRef: AnimatedRef<AnimatedScrollView> | null,
30 providedOffset?: SharedValue<number>
31): SharedValue<number> {
32 const internalOffset = useSharedValue(0);
33 const offset = useRef(providedOffset ?? internalOffset).current;
34
35 const eventHandler = useCallback(() => {
36 'worklet';
37 if (animatedRef) {
38 const element = getWebScrollableElement(animatedRef.current);
39 // scrollLeft is the X axis scrolled offset, works properly also with RTL layout
40 offset.value =
41 element.scrollLeft === 0 ? element.scrollTop : element.scrollLeft;
42 }
43 // eslint-disable-next-line react-hooks/exhaustive-deps
44 }, [animatedRef, animatedRef?.current]);
45
46 useEffect(() => {
47 const element = animatedRef?.current
48 ? getWebScrollableElement(animatedRef.current)
49 : null;
50
51 if (element) {
52 element.addEventListener('scroll', eventHandler);
53 }
54 return () => {
55 if (element) {
56 element.removeEventListener('scroll', eventHandler);
57 }
58 };
59 // React here has a problem with `animatedRef.current` since a Ref .current
60 // field shouldn't be used as a dependency. However, in this case we have
61 // to do it this way.
62 // eslint-disable-next-line react-hooks/exhaustive-deps
63 }, [animatedRef, animatedRef?.current, eventHandler]);
64
65 return offset;
66}
67
68function useScrollViewOffsetNative(
69 animatedRef: AnimatedRef<AnimatedScrollView> | null,
70 providedOffset?: SharedValue<number>
71): SharedValue<number> {
72 const internalOffset = useSharedValue(0);
73 const offset = useRef(providedOffset ?? internalOffset).current;
74
75 const eventHandler = useEvent<RNNativeScrollEvent>(
76 (event: ReanimatedScrollEvent) => {
77 'worklet';
78 offset.value =
79 event.contentOffset.x === 0
80 ? event.contentOffset.y
81 : event.contentOffset.x;
82 },
83 scrollNativeEventNames
84 // Read https://github.com/software-mansion/react-native-reanimated/pull/5056
85 // for more information about this cast.
86 ) as unknown as EventHandlerInternal<ReanimatedScrollEvent>;
87
88 useEffect(() => {
89 const elementTag = animatedRef?.getTag() ?? null;
90
91 if (elementTag) {
92 eventHandler.workletEventHandler.registerForEvents(elementTag);
93 }
94 return () => {
95 if (elementTag) {
96 eventHandler.workletEventHandler.unregisterFromEvents(elementTag);
97 }
98 };
99 // React here has a problem with `animatedRef.current` since a Ref .current
100 // field shouldn't be used as a dependency. However, in this case we have
101 // to do it this way.
102 // eslint-disable-next-line react-hooks/exhaustive-deps
103 }, [animatedRef, animatedRef?.current, eventHandler]);
104
105 return offset;
106}
107
108function getWebScrollableElement(
109 scrollComponent: AnimatedScrollView | null
110): HTMLElement {
111 return (
112 (scrollComponent?.getScrollableNode() as unknown as HTMLElement) ??
113 scrollComponent
114 );
115}
116
117const scrollNativeEventNames = [
118 'onScroll',
119 'onScrollBeginDrag',
120 'onScrollEndDrag',
121 'onMomentumScrollBegin',
122 'onMomentumScrollEnd',
123];