DEV Community

Cover image for Creating a Smooth Animated Vertical List in React Native with Reanimated
Amit Kumar
Amit Kumar

Posted on

Creating a Smooth Animated Vertical List in React Native with Reanimated

When it comes to mobile UI, animations can elevate the user experience, making interactions feel more fluid and engaging. In this blog, we’ll build an animated vertical list using react-native-reanimated that creates a smooth scrolling effect with opacity and scaling transitions.


Image description


Prerequisites

Before we start, ensure you have the following installed in your React Native project:

npm install react-native-reanimated react-native-gesture-handler
Enter fullscreen mode Exit fullscreen mode

Also, update your babel.config.js file to enable Reanimated’s plugin:

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};
Enter fullscreen mode Exit fullscreen mode

Building the Animated Vertical List

1. Import Required Dependencies

We begin by importing essential components from react-native and react-native-reanimated:

import { Dimensions, Image, StyleSheet, Text, View } from 'react-native';
import React from 'react';
import Animated, {
  interpolate,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useSharedValue,
} from 'react-native-reanimated';
Enter fullscreen mode Exit fullscreen mode

2. Define the Vertical List Component

const VerticalList = ({ data = [] }) => {
  const { height } = Dimensions.get('screen');
  const spacing = 8;
  const itemSize = height * 0.72;
  const itemFullSize = itemSize + spacing * 2;
  const scrollY = useSharedValue(0);

  const onScroll = useAnimatedScrollHandler(event => {
    scrollY.value = event.contentOffset.y / itemFullSize;
  });
Enter fullscreen mode Exit fullscreen mode

We define the item size relative to the screen height and calculate spacing accordingly.

3. Create the Animated Card Component

Each item in our list will have an animated opacity and scale effect based on its position in the list.



export const DATA = [
  {
    id: 'V_ID_1',
    title: 'Iron Man',
    thumbnailUrl:
      'https://ew.com/thmb/OEm1NLRTUG5HuHswbWLjvLJ5GIg=/1500x0/filters:no_upscale():max_bytes(150000):strip_icc():format(webp)/iron-man-2000-c36c45429d5148e5a5871a887cc3d96c.jpg',
    duration: '8:18',
    uploadTime: 'May 9, 2011',
    views: '24,969,123',
    author: 'Vlc Media Player',
    videoUrl:
      'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
    audioUrl:
      'https://github.com/rafaelreis-hotmart/Audio-Sample-files/raw/master/sample.mp3',
    description:
      'A thrilling superhero movie following Tony Stark, a billionaire genius who becomes Iron Man after creating a powerful armored suit to fight injustice.',
    subscriber: '25,254,545 Subscribers',
    isLive: true,
  },
// rest data
]


const AnimationCard = ({ item, index, scrollY }) => {
    const animatedStyle = useAnimatedStyle(() => {
      return {
        opacity: interpolate(scrollY.value, [index - 1, index, index + 1], [0.3, 1, 0.3]),
        transform: [
          {
            scale: interpolate(scrollY.value, [index - 1, index, index + 1], [0.92, 1, 0.92]),
          },
        ],
      };
    });

    return (
      <Animated.View style={[styles.card, animatedStyle]}>
        <Image source={{ uri: item.thumbnailUrl }} style={styles.backgroundImage} blurRadius={50} />
        <Image source={{ uri: item.thumbnailUrl }} style={styles.thumbnail} resizeMode="cover" />
        <View style={styles.textContainer}>
          <Text style={styles.title}>{item.title}</Text>
          <Text style={styles.description} numberOfLines={3}>{item.description}</Text>
        </View>
        <View style={styles.authorContainer}>
          <Image source={{ uri: item.author.avatar }} style={styles.avatar} />
          <Text style={styles.authorName}>{item.author.name}</Text>
        </View>
      </Animated.View>
    );
  };
Enter fullscreen mode Exit fullscreen mode

4. Render the Animated List

const renderItem = ({ item, index }) => <AnimationCard item={item} index={index} scrollY={scrollY} />;

  return (
    <Animated.FlatList
      data={data}
      renderItem={renderItem}
      contentContainerStyle={styles.listContainer}
      snapToInterval={itemFullSize}
      decelerationRate={'fast'}
      onScroll={onScroll}
      scrollEventThrottle={16}
    />
  );
};
Enter fullscreen mode Exit fullscreen mode

5. Define the Styles

To keep our component clean, let’s define a StyleSheet:

const styles = StyleSheet.create({
  card: {
    flex: 1,
    height: Dimensions.get('screen').height * 0.72,
    padding: 16,
    borderRadius: 8,
    gap: 8,
  },
  backgroundImage: {
    ...StyleSheet.absoluteFillObject,
    borderRadius: 12,
  },
  thumbnail: {
    flex: 1,
    height: Dimensions.get('screen').height * 0.4,
  },
  textContainer: {
    gap: 8,
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    color: '#fff',
  },
  description: {
    color: '#ddd',
  },
  authorContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },
  avatar: {
    width: 24,
    aspectRatio: 1,
    borderRadius: 12,
  },
  authorName: {
    fontSize: 12,
    color: '#ddd',
  },
  listContainer: {
    paddingHorizontal: 24,
    paddingVertical: (Dimensions.get('screen').height - Dimensions.get('screen').height * 0.72) / 2,
    gap: 16,
  },
});
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

By using Reanimated’s useSharedValue, useAnimatedScrollHandler, and useAnimatedStyle, we’ve created a beautiful and responsive animated vertical list. This technique makes the UI more engaging and can be used for displaying articles, media content, or product listings.

Let me know in the comments if you have any questions or enhancements! 🚀

Top comments (0)