Blur Carousel

A horizontal carousel where item scale and fade as you scroll with a soft blur

Last updated on

Edit on GitHub

Manual

Install the following dependencies:

npm install react-native-reanimated @sbaiahmed1/react-native-blur

Copy and paste the following code into your project. component/molecules/blur-carousel

import { Dimensions, StyleSheet, View, ViewStyle } from "react-native";import React, { memo } from "react";import { BlurCarouselItemProps, BlurCarouselProps } from "./types";import { BlurView, type BlurViewProps } from "@sbaiahmed1/react-native-blur";import Animated, {  interpolate,  useAnimatedScrollHandler,  useAnimatedStyle,  useSharedValue,  Extrapolation,  useAnimatedProps,} from "react-native-reanimated";const AnimatedBlurView =  Animated.createAnimatedComponent<BlurViewProps>(BlurView);const { width: SCREEN_WIDTH } = Dimensions.get("window");const ITEM_WIDTH = SCREEN_WIDTH * 0.75;const SPACING = 20;const SIDE_SPACING = (SCREEN_WIDTH - ITEM_WIDTH) / 2;const CarouselItem = <ItemT,>({  item,  index,  scrollX,  renderItem,  itemWidth = ITEM_WIDTH,  spacing = SPACING,}: BlurCarouselItemProps<ItemT>): React.ReactNode &  React.JSX.Element &  React.ReactElement => {  const animatedStyle = useAnimatedStyle<    Required<Partial<Pick<ViewStyle, "transform" | "opacity">>>  >(() => {    const inputRange = [      (index - 1) * itemWidth,      index * itemWidth,      (index + 1) * itemWidth,    ];    const scale = interpolate(      scrollX.value,      inputRange,      [0.85, 1, 0.85],      Extrapolation.CLAMP,    );    const opacity = interpolate(      scrollX.value,      inputRange,      [0.5, 1, 0.5],      Extrapolation.CLAMP,    );    return {      transform: [{ scale }],      opacity,    };  });  const animatedBlurProps = useAnimatedProps(() => {    const inputRange = [      (index - 1) * itemWidth,      index * itemWidth,      (index + 1) * itemWidth,    ];    const blurIntensity = interpolate(      scrollX.value,      inputRange,      [25, 0, 25],      Extrapolation.CLAMP,    );    return {      blurAmount: blurIntensity,    };  });  return (    <Animated.View      style={[        styles.itemContainer,        animatedStyle,        {          width: itemWidth,        },      ]}    >      <View        style={[          styles.itemContent,          {            width: itemWidth - spacing * 2,          },        ]}      >        {renderItem({ item, index })}        <AnimatedBlurView          style={[StyleSheet.absoluteFillObject, styles.blurOverlay]}          blurType="light"          animatedProps={animatedBlurProps}          reducedTransparencyFallbackColor="transparent"        />      </View>    </Animated.View>  );};const BlurCarousel = <ItemT,>({  data,  renderItem,  horizontalSpacing = SIDE_SPACING,  itemWidth = ITEM_WIDTH,  spacing = SPACING,}: BlurCarouselProps<ItemT>) => {  const scrollX = useSharedValue(0);  const onScroll = useAnimatedScrollHandler({    onScroll: (event) => {      scrollX.value = event.contentOffset.x;    },  });  return (    <Animated.FlatList      data={data}      showsHorizontalScrollIndicator={false}      onScroll={onScroll}      scrollEventThrottle={16}      keyExtractor={(_, index) => index.toString()}      horizontal      pagingEnabled      snapToInterval={itemWidth}      decelerationRate="fast"      contentContainerStyle={{        paddingHorizontal: horizontalSpacing,      }}      style={{ flexGrow: 0 }}      renderItem={({ item, index }) => (        <CarouselItem          item={item}          index={index}          scrollX={scrollX}          renderItem={renderItem}          itemWidth={itemWidth}          spacing={spacing}        />      )}    />  );};const styles = StyleSheet.create({  itemContainer: {    justifyContent: "center",    alignItems: "center",  },  itemContent: {    borderRadius: 20,    overflow: "hidden",    shadowColor: "#000",    shadowOffset: {      width: 0,      height: 8,    },    shadowOpacity: 0.2,    shadowRadius: 12,    elevation: 10,  },  blurOverlay: {    borderRadius: 20,  },});export { BlurCarousel, BlurCarouselItemProps, BlurCarouselProps };

Usage

import { View, Text, StyleSheet } from "react-native";import { GestureHandlerRootView } from "react-native-gesture-handler";import { StatusBar } from "expo-status-bar";import { useFonts } from "expo-font";import { SymbolView } from "expo-symbols";import { BlurCarousel } from "@/components/molecules/blur-carousel";import { LinearGradient } from "expo-linear-gradient";export default function App() {  const [fontLoaded] = useFonts({    SfProRounded: require("@/assets/fonts/sf-pro-rounded.ttf"),    Coolvetica: require("@/assets/fonts/CoolveticaLt-Regular.ttf"),    HelveticaNowDisplay: require("@/assets/fonts/HelveticaNowDisplayMedium.ttf"),  });  const DATA = [    {      id: "1",      title: "Design",      subtitle: "Create something beautiful",      description: "Transform ideas into stunning visuals with intuitive tools",      icon: "paintbrush.fill",      gradient: ["#ff375f", "#ff6b8a"],      stats: { value: "2.4k", label: "Projects" },    },    {      id: "2",      title: "Develop",      subtitle: "Build with precision",      description: "Write clean code and ship features faster than ever",      icon: "chevron.left.forwardslash.chevron.right",      gradient: ["#5e5ce6", "#8b8bf5"],      stats: { value: "18ms", label: "Response" },    },    {      id: "3",      title: "Launch",      subtitle: "Ship to the world",      description: "Deploy globally with confidence and reliability",      icon: "paperplane.fill",      gradient: ["#30d158", "#5de37a"],      stats: { value: "99.9%", label: "Uptime" },    },  ];  return (    <GestureHandlerRootView style={styles.container}>      <StatusBar style="light" />      <View style={styles.header}>        <Text          style={[            styles.title,            fontLoaded && { fontFamily: "HelveticaNowDisplay" },          ]}        >          Workflow        </Text>      </View>      <BlurCarousel        data={DATA}        renderItem={({ item }) => (          <View style={styles.card}>            <LinearGradient              colors={item.gradient as any}              start={{ x: 0, y: 0 }}              end={{ x: 1, y: 1 }}              style={styles.cardGradient}            />            <View style={styles.cardTop}>              <View style={styles.cardIcon}>                <SymbolView                  name={item.icon as any}                  size={28}                  tintColor="#fff"                />              </View>              <View style={styles.badge}>                <SymbolView name="star.fill" size={10} tintColor="#fff" />                <Text                  style={[                    styles.badgeText,                    fontLoaded && { fontFamily: "SfProRounded" },                  ]}                >                  Pro                </Text>              </View>            </View>            <View style={styles.cardMiddle}>              <Text                style={[                  styles.cardTitle,                  fontLoaded && { fontFamily: "Coolvetica" },                ]}              >                {item.title}              </Text>              <Text                style={[                  styles.cardSubtitle,                  fontLoaded && { fontFamily: "SfProRounded" },                ]}              >                {item.subtitle}              </Text>              <Text                style={[                  styles.cardDescription,                  fontLoaded && { fontFamily: "SfProRounded" },                ]}              >                {item.description}              </Text>            </View>            <View style={styles.cardBottom}>              <View style={styles.statBox}>                <Text                  style={[                    styles.statValue,                    fontLoaded && { fontFamily: "HelveticaNowDisplay" },                  ]}                >                  {item.stats.value}                </Text>                <Text                  style={[                    styles.statLabel,                    fontLoaded && { fontFamily: "SfProRounded" },                  ]}                >                  {item.stats.label}                </Text>              </View>              <View style={styles.arrowButton}>                <SymbolView name="arrow.right" size={18} tintColor="#fff" />              </View>            </View>          </View>        )}      />    </GestureHandlerRootView>  );}const styles = StyleSheet.create({  container: {    flex: 1,    backgroundColor: "#0a0a0a",  },  header: {    paddingHorizontal: 20,    paddingTop: 70,    paddingBottom: 40,  },  title: {    fontSize: 42,    fontWeight: "700",    color: "#fff",  },  card: {    width: "100%",    height: 380,    borderRadius: 28,    overflow: "hidden",    padding: 24,    justifyContent: "space-between",  },  cardGradient: {    ...StyleSheet.absoluteFillObject,  },  cardTop: {    flexDirection: "row",    justifyContent: "space-between",    alignItems: "flex-start",  },  cardIcon: {    width: 60,    height: 60,    borderRadius: 20,    backgroundColor: "rgba(255,255,255,0.2)",    justifyContent: "center",    alignItems: "center",  },  badge: {    flexDirection: "row",    alignItems: "center",    gap: 4,    backgroundColor: "rgba(0,0,0,0.2)",    paddingHorizontal: 10,    paddingVertical: 6,    borderRadius: 12,  },  badgeText: {    fontSize: 12,    fontWeight: "600",    color: "#fff",  },  cardMiddle: {    gap: 8,  },  cardTitle: {    fontSize: 44,    fontWeight: "600",    color: "#fff",  },  cardSubtitle: {    fontSize: 18,    fontWeight: "600",    color: "rgba(255,255,255,0.9)",  },  cardDescription: {    fontSize: 14,    color: "rgba(255,255,255,0.6)",    lineHeight: 20,    marginTop: 4,  },  cardBottom: {    flexDirection: "row",    justifyContent: "space-between",    alignItems: "flex-end",  },  statBox: {    gap: 2,  },  statValue: {    fontSize: 32,    fontWeight: "700",    color: "#fff",  },  statLabel: {    fontSize: 13,    color: "rgba(255,255,255,0.6)",  },  arrowButton: {    width: 48,    height: 48,    borderRadius: 24,    backgroundColor: "rgba(0,0,0,0.2)",    justifyContent: "center",    alignItems: "center",  },});

Props

BlurCarouselItemProps

React Native Reanimated
React Native Blur