Animating a Heart Button

Posted in react-native, react-native-reanimated, animations on May 21, 2024 by Hemanta Sapkota ‐ 5 min read

Animating a Heart Button

Animating user interactions can greatly enhance the user experience of your application. One common example is animating a "like" button, often represented by a heart icon. In this blog post, we'll walk through the process of creating an animated heart button in React Native using react-native-reanimated and lucide-react-native.

Animation Preview

An animated heart button like the one shown above is commonly used in social media apps, e-commerce platforms, and other applications where users can like or favorite content. It provides visual feedback and enhances user engagement.

Core Concepts

Before diving into the implementation, let’s briefly discuss the core concepts used in this animation:

  • useSharedValue: This hook from react-native-reanimated allows you to create shared values that can be used across multiple components and animations. In our case, we use it to share the animation state (scale and isLiked) between the animation and the component.

  • useAnimatedStyle: Another hook from react-native-reanimated that allows you to create animated styles based on shared values or other animations.

  • withSpring: A function from react-native-reanimated that creates a spring-based animation. It takes a target value and optional configuration options for damping and stiffness, allowing you to create smooth, physics-based animations.

Performance Considerations

While react-native-reanimated is designed to be highly performant, it’s essential to be mindful of potential performance issues, especially when dealing with complex animations or large component trees. In our implementation, we’re only animating a single component, so performance should not be a significant concern. However, if you plan to use this approach with a large number of animated components, it’s recommended to profile your application and optimize as needed.

Accessibility Considerations

Animations can sometimes pose accessibility challenges, especially for users with cognitive or motor disabilities. In the case of our animated heart button, it’s essential to ensure that the button remains accessible and functional even without the animation. Additionally, you should provide alternative ways for users to interact with the button, such as keyboard navigation or voice control.

  1. Provide alternative text descriptions: For users who rely on screen readers or have visual impairments, it’s important to provide alternative text descriptions that accurately describe the button’s functionality. This can be achieved using the accessibilityLabel prop in React Native.

  2. Ensure keyboard accessibility: Make sure the button can be focused and activated using keyboard navigation. This can be achieved by setting the accessible and accessibilityRole props appropriately.

  3. Ensure sufficient contrast: Make sure the colors used for the heart icon and the background have sufficient contrast to meet accessibility guidelines. This will ensure that the button is easily visible to users with low vision or color blindness.

Full Source Code

Here’s the full source code for the animated heart button:

import React, {useState} from 'react';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
import {LucideHeart} from 'lucide-react-native';

export const APP_ICON_SIZE = 24;

const size = APP_ICON_SIZE * 2;

const AnimatedHeart = Animated.createAnimatedComponent(LucideHeart);

type Props = {
  primary: string;
  accent: string;
  onPress: () => void;
};

export const AnimatedLikeButton = ({primary, accent, onPress}: Props) => {
  const scale = useSharedValue(1);
  const isLiked = useSharedValue(true);
  const [iconColor, setIconColor] = useState(primary);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{scale: scale.value}],
    };
  });

  const handlePress = () => {
    scale.value = withSpring(
      1.5,
      {
        damping: 2,
        stiffness: 150,
      },
      () => {
        scale.value = withSpring(1);
      },
    );
    isLiked.value = !isLiked.value;
    if (onPress) {
      runOnJS(onPress)();
    }
    runOnJS(setIconColor)(isLiked.value ? accent : primary);
  };

  return (
    <AnimatedHeart
      size={size}
      color={iconColor}
      fill={iconColor}
      style={animatedStyle}
      onPress={handlePress}
      /* Accessibility Props */
      accessible={true}
      accessibilityRole="button"
      accessibilityLabel={isLiked.value ? 'Unlike' : 'Like'}
    />
  );
};

Step-by-Step Guide

1. Setup

First, ensure you have react-native-reanimated and lucide-react-native installed in your project:

npm install react-native-reanimated lucide-react-native

2. Import Required Modules

In the component file, import the necessary modules from react-native, react-native-reanimated, and lucide-react-native:

import React, {useState} from 'react';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
import {LucideHeart} from 'lucide-react-native';

3. Define Constants

Define the constants for the icon size:

export const APP_ICON_SIZE = 24;
const size = APP_ICON_SIZE * 2;
const AnimatedHeart = Animated.createAnimatedComponent(LucideHeart);

4. Create the Animated Button Component

Define the AnimatedLikeButton component with its props:

type Props = {
  primary: string;
  accent: string;
  onPress: () => void;
};

export const AnimatedLikeButton = ({primary, accent, onPress}: Props) => {
  const scale = useSharedValue(1);
  const isLiked = useSharedValue(true);
  const [iconColor, setIconColor] = useState(primary);

5. Define Animated Styles

Use the useAnimatedStyle hook to define the animated style for the heart icon:

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{scale: scale.value}],
    };
  });

6. Handle Press Events

Implement the handlePress function to animate the button and toggle the liked state:

  const handlePress = () => {
    scale.value = withSpring(
      1.5,
      {
        damping: 2,
        stiffness: 150,
      },
      () => {
        scale.value = withSpring(1);
      },
    );
    isLiked.value = !isLiked.value;
    if (onPress) {
      runOnJS(onPress)();
    }
    runOnJS(setIconColor)(isLiked.value ? accent : primary);
  };

7. Render the Component

Finally, render the AnimatedHeart component with the animated styles and press handler:


  return (
    <AnimatedHeart
      size={size}
      color={iconColor}
      fill={iconColor}
      style={animatedStyle}
      onPress={handlePress}
      /* Accessibility Props */
      accessible={true}
      accessibilityRole="button"
      accessibilityLabel={isLiked.value ? 'Unlike' : 'Like'}
    />
  );
};

Customization Options

The AnimatedLikeButton component is designed to be flexible and customizable. You can adjust the following properties to suit your needs:

  • primary: The primary color of the heart icon when it’s in the “unliked” state.
  • accent: The accent color of the heart icon when it’s in the “liked” state.
  • onPress: A callback function that gets called when the button is pressed. This allows you to handle additional logic, such as updating the like count or persisting the like state in your application.

Conclusion

The core insight to animating the heart button is the combination of visual feedback and state management. By leveraging react-native-reanimated’s useSharedValue and useAnimatedStyle, we create smooth, spring-based animations that react to user interactions in real-time. This not only makes the button visually appealing but also provides immediate feedback, enhancing user engagement and experience.

comments powered by Disqus