react-native-reanimated#runOnUI TypeScript Examples

The following examples show how to use react-native-reanimated#runOnUI. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: common.ts    From react-native-gallery-toolkit with MIT License 6 votes vote down vote up
export function runOnJSorUI(cb: any) {
  'worklet';

  console.log(Object.keys(cb));

  if (cb && cb.__worklet) {
    return runOnUI(cb);
  }

  return runOnJS(cb);
}
Example #2
Source File: useInit.ts    From react-native-gallery-toolkit with MIT License 6 votes vote down vote up
export function useInit() {
  useRunOnce(
    runOnUI(() => {
      'worklet';

      const x: { [key: string]: any } = {};
      Object.keys(usedWorklets).forEach((key) => {
        x[key] = usedWorklets[key];
      });
    }),
  );
}
Example #3
Source File: Pager.tsx    From react-native-gallery-toolkit with MIT License 4 votes vote down vote up
Pager = typedMemo(function Pager<
  TPages,
  ItemT = UnpackItemT<TPages>,
>({
  pages,
  initialIndex,
  totalCount,
  numToRender = 2,
  onIndexChange = workletNoop,
  renderPage,
  width = dimensions.width,
  gutterWidth = GUTTER_WIDTH,
  shouldRenderGutter = true,
  keyExtractor,
  pagerWrapperStyles = {},
  getItem,
  springConfig,
  onPagerTranslateChange = workletNoop,
  onGesture = workletNoop,
  onEnabledGesture = workletNoop,
  shouldHandleGestureEvent = workletNoopTrue,
  initialDiffValue = 0,
  shouldUseInteractionManager = true,
  outerGestureHandlerRefs = [],
  verticallyEnabled = true,
}: PagerProps<TPages, ItemT>) {
  assertWorklet(onIndexChange);
  assertWorklet(onPagerTranslateChange);
  assertWorklet(onGesture);
  assertWorklet(onEnabledGesture);
  assertWorklet(shouldHandleGestureEvent);

  // make sure to not calculate translate with gutter
  // if we don't want to render it
  if (!shouldRenderGutter) {
    gutterWidth = 0;
  }

  const getPageTranslate = useWorkletCallback(
    (i: number) => {
      const t = i * width;
      const g = gutterWidth * i;
      return -(t + g);
    },
    [gutterWidth, width],
  );

  const pagerRef = useRef(null);
  const tapRef = useRef(null);

  const isActive = useSharedValue(true);

  const onPageStateChange = useWorkletCallback((value: boolean) => {
    isActive.value = value;
  }, []);

  const velocity = useSharedValue(0);
  const isMounted = useRef(false);

  const [diffValue, setDiffValue] = useState(initialDiffValue);
  useEffect(() => {
    if (shouldUseInteractionManager && !isMounted.current) {
      InteractionManager.runAfterInteractions(() => {
        setDiffValue(numToRender);
        console.log('numToRender in interaction');
      });
      isMounted.current = true;
    } else {
      setDiffValue(numToRender);
    }
  }, [numToRender]);

  // S2: Pager related stuff
  const [activeIndex, setActiveIndex] = useState(initialIndex);

  const index = useSharedValue(initialIndex);
  const length = useSharedValue(totalCount);
  const pagerX = useSharedValue(0);
  const toValueAnimation = useSharedValue(
    getPageTranslate(initialIndex),
  );

  const offsetX = useSharedValue(getPageTranslate(initialIndex));

  const totalWidth = useDerivedValue(() => {
    return length.value * width + gutterWidth * length.value - 2;
  }, []);

  const onIndexChangeCb = useWorkletCallback((nextIndex: number) => {
    onIndexChange(nextIndex);

    runOnJS(setActiveIndex)(nextIndex);
  }, []);

  const onIndexChangeWorklet = useWorkletCallback((i: number) => {
    offsetX.value = getPageTranslate(i);
    index.value = i;
    onIndexChangeCb(i);
  }, []);

  useEffect(() => {
    runOnUI(onIndexChangeWorklet)(initialIndex);
  }, [initialIndex]);

  const getSpringConfig = useWorkletCallback(
    (noVelocity?: boolean) => {
      const ratio = 1.1;
      const mass = 0.4;
      const stiffness = IS_ANDROID ? 200.0 : 100.0;
      const damping = ratio * 2.0 * Math.sqrt(mass * stiffness);

      const configToUse =
        typeof springConfig !== 'undefined'
          ? springConfig
          : {
              stiffness,
              mass,
              damping,
              restDisplacementThreshold: 1,
              restSpeedThreshold: 5,
            };

      // @ts-ignore
      // cannot use merge and spread here :(
      configToUse.velocity = noVelocity ? 0 : velocity.value;

      return configToUse;
    },
    [springConfig],
  );

  const onChangePageAnimation = useWorkletCallback(
    (noVelocity?: boolean) => {
      const config = getSpringConfig(noVelocity);

      if (offsetX.value === toValueAnimation.value) {
        return;
      }

      offsetX.value = withSpring(
        toValueAnimation.value,
        config,
        (isCanceled) => {
          if (!isCanceled) {
            velocity.value = 0;
          }
        },
      );
    },
    [getSpringConfig],
  );

  // S3 Pager
  const getCanSwipe = useWorkletCallback(
    (currentTranslate: number = 0) => {
      const nextTranslate = offsetX.value + currentTranslate;

      if (nextTranslate > 0) {
        return false;
      }

      const totalTranslate =
        width * (length.value - 1) + gutterWidth * (length.value - 1);

      if (Math.abs(nextTranslate) >= totalTranslate) {
        return false;
      }

      return true;
    },
    [width, gutterWidth],
  );

  const getNextIndex = useWorkletCallback(
    (v: number) => {
      const currentTranslate = Math.abs(
        getPageTranslate(index.value),
      );
      const currentIndex = index.value;
      const currentOffset = Math.abs(offsetX.value);

      const nextIndex = v < 0 ? currentIndex + 1 : currentIndex - 1;

      if (
        nextIndex < currentIndex &&
        currentOffset > currentTranslate
      ) {
        return currentIndex;
      }

      if (
        nextIndex > currentIndex &&
        currentOffset < currentTranslate
      ) {
        return currentIndex;
      }

      if (nextIndex > length.value - 1 || nextIndex < 0) {
        return currentIndex;
      }

      return nextIndex;
    },
    [getPageTranslate],
  );

  const isPagerInProgress = useDerivedValue(() => {
    return (
      Math.floor(Math.abs(getPageTranslate(index.value))) !==
      Math.floor(Math.abs(offsetX.value + pagerX.value))
    );
  }, [getPageTranslate]);

  const onPan = useAnimatedGestureHandler<
    PanGestureHandlerGestureEvent,
    {
      pagerActive: boolean;
      offsetX: null | number;
    }
  >({
    onGesture: (evt) => {
      onGesture(evt, isActive);

      if (isActive.value && !isPagerInProgress.value) {
        onEnabledGesture(evt);
      }
    },

    onInit: (_, ctx) => {
      ctx.offsetX = null;
    },

    shouldHandleEvent: (evt) => {
      return (
        (evt.numberOfPointers === 1 &&
          isActive.value &&
          Math.abs(evt.velocityX) > Math.abs(evt.velocityY) &&
          shouldHandleGestureEvent(evt)) ||
        isPagerInProgress.value
      );
    },

    onEvent: (evt) => {
      velocity.value = clampVelocity(
        evt.velocityX,
        MIN_VELOCITY,
        MAX_VELOCITY,
      );
    },

    onStart: (_, ctx) => {
      ctx.offsetX = null;
    },

    onActive: (evt, ctx) => {
      // workaround alert
      // the event triggers with a delay and first frame value jumps
      // we capture that value and subtract from the actual one
      // so the translate happens on a second frame
      if (ctx.offsetX === null) {
        ctx.offsetX =
          evt.translationX < 0 ? evt.translationX : -evt.translationX;
      }

      const val = evt.translationX - ctx.offsetX;

      const canSwipe = getCanSwipe(val);
      pagerX.value = canSwipe ? val : friction(val);
    },

    onEnd: (evt, ctx) => {
      const val = evt.translationX - ctx.offsetX!;

      const canSwipe = getCanSwipe(val);

      offsetX.value += pagerX.value;
      pagerX.value = 0;

      const nextIndex = getNextIndex(evt.velocityX);

      const vx = Math.abs(evt.velocityX);

      const translation = Math.abs(val);
      const isHalf = width / 2 < translation;

      const shouldMoveToNextPage = (vx > 10 || isHalf) && canSwipe;

      // we invert the value since the translationY is left to right
      toValueAnimation.value = -(shouldMoveToNextPage
        ? -getPageTranslate(nextIndex)
        : -getPageTranslate(index.value));

      onChangePageAnimation(!shouldMoveToNextPage);

      if (shouldMoveToNextPage) {
        index.value = nextIndex;
        onIndexChangeCb(nextIndex);
      }
    },
  });

  const onTap = useAnimatedGestureHandler({
    shouldHandleEvent: (evt) => {
      return evt.numberOfPointers === 1 && isActive.value;
    },

    onStart: () => {
      cancelAnimation(offsetX);
    },

    onEnd: () => {
      onChangePageAnimation(true);
    },
  });

  const pagerStyles = useAnimatedStyle<ViewStyle>(() => {
    const translateX = pagerX.value + offsetX.value;

    onPagerTranslateChange(translateX);

    return {
      width: totalWidth.value,
      transform: [
        {
          translateX,
        },
      ],
    };
  }, []);

  const pagerRefs = useMemo<PageRefs>(() => [pagerRef, tapRef], []);

  const pagesToRender = useMemo(() => {
    const temp = [];

    for (let i = 0; i < totalCount; i += 1) {
      let itemToUse;

      if (typeof getItem === 'function') {
        itemToUse = getItem(pages, i);
      } else if (Array.isArray(pages)) {
        itemToUse = pages[i];
      } else {
        throw new Error(
          'Pager: items either should be an array of getItem should be defined',
        );
      }

      const shouldRender = getShouldRender(i, activeIndex, diffValue);

      if (!shouldRender) {
        temp.push(null);
      } else {
        temp.push(
          <Page
            key={keyExtractor(itemToUse, i)}
            item={itemToUse}
            currentIndex={index}
            pagerRefs={pagerRefs}
            onPageStateChange={onPageStateChange}
            index={i}
            length={totalCount}
            gutterWidth={gutterWidth}
            renderPage={renderPage}
            getPageTranslate={getPageTranslate}
            width={width}
            isPagerInProgress={isPagerInProgress}
            shouldRenderGutter={shouldRenderGutter}
          />,
        );
      }
    }

    return temp;
  }, [
    activeIndex,
    diffValue,
    keyExtractor,
    getItem,
    totalCount,
    pages,
    getShouldRender,
    index,
    pagerRefs,
    onPageStateChange,
    gutterWidth,
    renderPage,
    getPageTranslate,
    width,
    isPagerInProgress,
    shouldRenderGutter,
  ]);

  return (
    <View style={StyleSheet.absoluteFillObject}>
      <Animated.View style={[StyleSheet.absoluteFill]}>
        <PanGestureHandler
          ref={pagerRef}
          minDist={0.1}
          minVelocityX={0.1}
          activeOffsetX={[-4, 4]}
          activeOffsetY={verticallyEnabled ? [-4, 4] : undefined}
          simultaneousHandlers={[tapRef, ...outerGestureHandlerRefs]}
          onGestureEvent={onPan}
        >
          <Animated.View style={StyleSheet.absoluteFill}>
            <TapGestureHandler
              ref={tapRef}
              maxDeltaX={10}
              maxDeltaY={10}
              simultaneousHandlers={pagerRef}
              onGestureEvent={onTap}
            >
              <Animated.View
                style={[StyleSheet.absoluteFill, pagerWrapperStyles]}
              >
                <Animated.View style={StyleSheet.absoluteFill}>
                  <Animated.View style={[styles.pager, pagerStyles]}>
                    {pagesToRender}
                  </Animated.View>
                </Animated.View>
              </Animated.View>
            </TapGestureHandler>
          </Animated.View>
        </PanGestureHandler>
      </Animated.View>
    </View>
  );
})