Neolex
Neolex

Reputation: 1

Why is the Expo bottom bar so slow? How can I optimise it?

I used a bottom tab bar from a youtube tutorial but its really slow between the time I click on a tab and the time its start moving and going to the other tab It take like 1 seconds between the moment I click on the tab and the moment the animation start. Do you have an idea of what could be the issue ? I think the interpolate path is not optimise but i'm a beginners on expo and animation here is what the tab bar looks like And the code is :

import React, { FC, useEffect } from "react";
import { StyleSheet, View } from "react-native";
import Svg, { Path } from "react-native-svg";
import Animated, {
  runOnJS,
  useAnimatedProps,
  useSharedValue,
  withTiming,
  Easing,
} from "react-native-reanimated";
import { interpolatePath } from "react-native-redash";
import { useRouter, usePathname } from "expo-router";

import { SCREEN_WIDTH } from "@/src/constants/screen";
import usePath from "@/src/hooks/usePath";
import { getPathXCenter } from "@/src/utils/path";
import TabItem from "./tabItem";
import AnimatedCircle from "./animatedCircle";
import HeadphoneIcon from "@/src/components/icons/headphoneIcon";
import HomeIcon from "@/src/components/icons/homeIcon";
import HistoryIcon from "@/src/components/icons/historyIcon";
import ProfileIcon from "@/src/components/icons/profileIcon";
import SettingsIcon from "../icons/settingsIcon";

const AnimatedPath = Animated.createAnimatedComponent(Path);

const TABS = [
  { name: "home", keyword: "home", label: "Home", icon: HomeIcon },
  { name: "playlistsList", keyword: "playlist", label: "Playlist", icon: HeadphoneIcon },
  { name: "history", keyword: "history", label: "History", icon: HistoryIcon },
  { name: "profile", keyword: "profile", label: "Profile", icon: ProfileIcon },
  { name: "settings", keyword: "settings", label: "Settings", icon: SettingsIcon },
];

interface CustomBottomTabProps {
  state: {
    index: number;
    routes: Array<{ name: string }>;
  };
  navigation: {
    navigate: (name: string) => void;
  };
}

export const CustomBottomTab: FC<CustomBottomTabProps> = ({ state, navigation }) => {
  const { containerPath, curvedPaths, tHeight } = usePath();
  const circleXCoordinate = useSharedValue(0);
  const progress = useSharedValue(1);
  const router = useRouter();
  const pathname = usePathname();
  const currentIndex = TABS.findIndex(tab => {
    if (tab.keyword === 'home' && pathname === '/') {
      return true;
    }
    return pathname.includes(tab.keyword);
  });
  const handleMoveCircle = (currentPath: string) => {
    circleXCoordinate.value = getPathXCenter(currentPath);
  };

  const selectIcon = (routeName: string, isActive: boolean) => {
    const color = isActive ? "#000000" : "#BABABA";
    const IconComponent = TABS.find(tab => tab.name === routeName)?.icon;
    return IconComponent ? <IconComponent color={color} /> : null;
  };

  useEffect(() => {
    if (currentIndex >= 0) {
      progress.value = withTiming(currentIndex + 1, {
        duration: 500,
        easing: Easing.bezier(0.25, 0.1, 0.25, 1),
      });
    }
  }, [pathname]);

  const animatedProps = useAnimatedProps(() => {
    console.log('progress.value', progress.value);
    const currentPath = interpolatePath(
      progress.value,
      Array.from({ length: curvedPaths.length }, (_, index) => index + 1),
      curvedPaths,
    );
    runOnJS(handleMoveCircle)(currentPath);
    return {
      d: `${containerPath} ${currentPath}`,
    };
  });

  const handleTabPress = (index: number, path: string) => {
    console.log('press', index);
   if (path === "home") {
    router.push(`/(auth)/(tabs)/`);
   } else {
    router.push(`/(auth)/(tabs)/${path}`);
   }
    progress.value = withTiming(index, {
      duration: 500,
      easing: Easing.bezier(0.25, 0.1, 0.25, 1),
    });
  };
  return (
    <View style={styles.tabBarContainer}>
      <Svg width={SCREEN_WIDTH} height={tHeight} style={styles.shadowMd}>
        <AnimatedPath fill={"white"} animatedProps={animatedProps} />
      </Svg>
      <AnimatedCircle circleX={circleXCoordinate} />
      <View
        style={[
          styles.tabItemsContainer,
          {
            height: tHeight,
          },
        ]}
      >
        {TABS.map((tab, index) => (
          <TabItem
            key={index.toString()}
            label={tab.label}
            icon={selectIcon(tab.name, currentIndex === index)}
            activeIndex={currentIndex + 1}
            index={index}
            onTabPress={() => handleTabPress(index + 1, tab.name)}
          />
        ))}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  tabBarContainer: {
    position: "absolute",
    bottom: 0,
    zIndex: 2,
  },
  tabItemsContainer: {
    position: "absolute",
    flexDirection: "row",
    width: "100%",
  },
  shadowMd: {
    elevation: 3,
    shadowColor: "#000",
    shadowOpacity: 0.2,
    shadowRadius: 3,
    shadowOffset: { width: 0, height: 3 },
  }
});

here is usePath

import { useMemo } from "react";
import { curveBasis, line } from "d3-shape";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { SCREEN_WIDTH } from "@/src/constants/screen";
import { parse } from "react-native-redash";

type GenerateTabShapePath = (
  position: number,
  adjustedHeight: number,
) => string;

const NUM_TABS = 5;
const SCALE = 0.6;
const TAB_BAR_HEIGHT = 50;

const generateTabShapePath: GenerateTabShapePath = (
  position,
  adjustedHeight,
) => {
  const adjustedWidth = SCREEN_WIDTH / NUM_TABS;
  const tabX = adjustedWidth * position;

  const lineGenerator = line().curve(curveBasis);
  const tab = lineGenerator([
    [tabX - 100 * SCALE, 0],
    [tabX - (65 + 35) * SCALE, 0],
    [tabX - (50 - 10) * SCALE, -4 * SCALE],
    [tabX - (50 - 10) * SCALE, (adjustedHeight - 14) * SCALE],
    [tabX + (50 - 15) * SCALE, (adjustedHeight - 14) * SCALE],
    [tabX + (50 - 10) * SCALE, -4 * SCALE],
    [tabX + (65 + 35) * SCALE, 0],
    [tabX + 100 * SCALE, 0],
  ]);

  return `${tab}`;
};

const usePath = () => {
  const insets = useSafeAreaInsets();
  const tHeight = TAB_BAR_HEIGHT + insets.bottom;
  const adjustedHeight = tHeight - insets.bottom;

  const containerPath = useMemo(() => {
    return `M0,0L${SCREEN_WIDTH},0L${SCREEN_WIDTH},0L${SCREEN_WIDTH},${tHeight}L0,${tHeight}L0,0`;
  }, [tHeight]);

  const curvedPaths = useMemo(() => {
    return Array.from({ length: NUM_TABS }, (_, index) => {
      const tabShapePath = generateTabShapePath(index + 0.5, adjustedHeight);
      return parse(`${tabShapePath}`);
    });
  }, [adjustedHeight]);

  return { containerPath, curvedPaths, tHeight };
};

export default usePath;

Upvotes: 0

Views: 33

Answers (0)

Related Questions