Building a Simple Alignment Button

Karthik Balasubramanian
Timeless
7 min readAug 24, 2023

--

Hey Everyone, in this blog we will see how to build a simple Alignment Button inspired from a Dribbble shot by Oleg Frolov.

Let us get started.

Cover Image

React Native Project Setup

You can use React Native’s built-in command line interface to generate a new project.

npx react-native@latest init AwesomeProject

The packages needed to get the Interaction done are just React Native Reanimated and Gesture Handler libraries.

For styling, I will use the twrnc package, an API for Tailwind CSS with React Native.

npm install --save twrnc

As we will be using SVG in our project, we need to install them as well

yarn add react-native-svg
// And Linking
cd ios && pod install

Furthermore, install react-native-reanimated and react-native-gesture-handler libraries.

yarn add react-native-reanimated react-native-gesture-handler

Get detailed instructions for reanimated here and gesture handler here.

That’s it for dependencies.

Let us run our React Native app now.

// For iOS Simulator
npx react-native run-ios
// For Android, make sure you have an Android Emulator running
npx react-native run-android

That’s all for setting up. 🎉

Structuring the Lines

We are going to code something like this:

Alignment Button

The code would look like this:

<Animated.View
style={[
tailwind.style(
"relative px-5 bg-white w-[250px] h-[100px] rounded-xl shadow-xl flex flex-row items-center justify-between",
),
containerStyle,
]}
>
<Pressable
onPressIn={handleOnPressIn}
onPress={() => {
alignState.value = 0;
verticalLine.value = withSpring(20, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(28, HorizontalSpringConfig),
);
}}
style={tailwind.style(
"flex flex-row items-center justify-start w-[70px]",
)}
>
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
<View style={tailwind.style("ml-1 h-2 w-6 bg-blue-100 rounded-sm")} />
</Pressable>
<Pressable
onPressIn={handleOnPressIn}
onPress={() => {
alignState.value = 1;
verticalLine.value = withSpring(122, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(112, HorizontalSpringConfig),
);
}}
style={tailwind.style(
"flex flex-row items-center justify-center w-[70px]",
)}
>
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
<View
style={tailwind.style("absolute h-2 w-6 bg-blue-100 rounded-sm")}
/>
</Pressable>
<Pressable
onPressIn={handleOnPressIn}
onPress={() => {
alignState.value = 2;
verticalLine.value = withSpring(226, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(198, HorizontalSpringConfig),
);
}}
style={tailwind.style(
"flex flex-row items-center justify-end w-[70px]",
)}
>
<View style={tailwind.style("h-2 w-6 bg-blue-100 rounded-sm mr-1")} />
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
</Pressable>
</Animated.View>

Let us break it down.

First, the code snippet has an Animated.View which wraps three Pressable components.

Those represent the touchable areas where users can interact to select the Alignment.

Every Pressable component, will have an onPressIn function definition,

  const handleOnPressIn = () => {
scaleAnim.value = withSpring(0.97, ScaleSpringConfig, finished => {
if (finished) {
scaleAnim.value = withDelay(10, withSpring(1));
}
});
};

This function initiates an animation using the scaleAnim SharedValue variable, which controls the scale of the touchable area to 97% of its original size.

Once the scaling animation finishes, it sets up another animation using the withDelay function that scales the component back to its original size.

Touch Scale Down Feedback

Next, we will define the onPress event handler that is triggered when the user releases the touch. We will be required to execute distinct actions based on the Touchable area that has been pressed.

Let us now create some more SharedValue variables.

  const verticalLine = useSharedValue(0);
const horizontalLine = useSharedValue(0);

const alignState = useSharedValue(0);

These are variables which will be used to indicate a selected state in alignment. We will now create an absolute positioned element, which will move on top of our component.

<Animated.View
style={tailwind.style("absolute flex flex-row items-center left-0")}
>
<Animated.View
style={[
tailwind.style("h-8 w-1 bg-blue-600 rounded-xl z-50"),
verticalLineStyle,
]}
/>
<Animated.View
style={[
tailwind.style("absolute h-2 w-6 bg-blue-600 rounded-sm z-50"),
horizontalLineStyle,
]}
/>
</Animated.View>

Now our button will look like this:

Selected Initial State

We might have to set some initial positioning.

As the padding to the container is 20px.

The verticalLine initial value can be set to 20px.

Similar to horizontalLine, the initial value is 20px + the width of the indicator + the space between the first vertical + horizontal line.

const verticalLine = useSharedValue(20);
const horizontalLine = useSharedValue(28);

Now we get this:

Update Initial State

Now for the onPress event handler, we will have to update the alignState value to 0, 1 and 2 to control the alignment or positioning of elements.

Similar to the initial states, we might have to calculate the other translation values for both verticalLine and horizontalLine for different alignState values.

The verticalLine will animate to new positions, [20, 122, 226] for the alignState values [0,1,2].

The horizontalLine will animate to new positions, [28, 112,198] for the alignState values [0,1,2].

We need the horizontalLine to follow the verticalLine so we animate that variable with a slight delay.

With which we will end up getting the interaction below.

Final Animation

After further refactoring, we will have code like this:

import React from "react";
import { View, Pressable } from "react-native";
import Animated, {
interpolate,
Layout,
useAnimatedStyle,
useSharedValue,
withDelay,
withSpring,
WithSpringConfig,
} from "react-native-reanimated";
import tailwind from "twrnc";

const VerticalSpringConfig: WithSpringConfig = {
mass: 1,
velocity: 1,
stiffness: 120,
damping: 20,
};
const HorizontalSpringConfig: WithSpringConfig = {
mass: 1,
velocity: 1,
stiffness: 120,
damping: 30,
};

const ScaleSpringConfig: WithSpringConfig = {
mass: 1,
velocity: 0,
stiffness: 120,
damping: 30,
};

export const AlignInteraction1 = () => {
const verticalLine = useSharedValue(0);
const horizontalLine = useSharedValue(0);

const scaleAnim = useSharedValue(1);

// The state for Text Style
const alignState = useSharedValue(0);

const verticalLineStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: interpolate(
verticalLine.value,
[0, 1, 2],
[20, 122, 226],
),
},
],
};
});
const horizontalLineStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX: interpolate(
horizontalLine.value,
[0, 1, 2],
[28, 112, 198],
),
},
],
};
});
const containerStyle = useAnimatedStyle(() => {
return {
transform: [
{
scale: scaleAnim.value,
},
],
};
});

const textAnimatedStyle = useAnimatedStyle(() => {
return {
textAlign:
alignState.value === 0
? "left"
: alignState.value === 1
? "center"
: alignState.value === 2
? "right"
: "auto",
};
});

const handleOnPressIn = () =>
(scaleAnim.value = withSpring(0.97, ScaleSpringConfig, finished => {
if (finished) {
scaleAnim.value = withDelay(10, withSpring(1));
}
}));

const handleOnPressLeftAlignment = () => {
alignState.value = 0;
verticalLine.value = withSpring(0, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(0, HorizontalSpringConfig),
);
};

const handleOnPressCenterAlignment = () => {
alignState.value = 1;
verticalLine.value = withSpring(1, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(1, HorizontalSpringConfig),
);
};

const handleOnPressRightAlignment = () => {
alignState.value = 2;
verticalLine.value = withSpring(2, VerticalSpringConfig);
horizontalLine.value = withDelay(
100,
withSpring(2, HorizontalSpringConfig),
);
};

return (
<View style={tailwind.style("flex-1 bg-white justify-center items-center")}>
<Animated.Text
layout={Layout.delay(1000).springify()}
style={[
tailwind.style("text-lg text-center px-10 py-4"),
textAnimatedStyle,
]}
>
Good design and development put the user at the center of the process.
This means understanding the user's needs, wants, and behaviors, and
designing products that address these.
</Animated.Text>
<Animated.View
style={[
tailwind.style(
"relative px-5 bg-white w-[250px] h-[100px] rounded-xl shadow-xl flex flex-row items-center justify-between",
),
containerStyle,
]}
>
<Pressable
onPressIn={handleOnPressIn}
onPress={handleOnPressLeftAlignment}
style={tailwind.style(
"flex flex-row items-center justify-start w-[70px] h-[70px]",
)}
>
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
<View style={tailwind.style("ml-1 h-2 w-6 bg-blue-100 rounded-sm")} />
</Pressable>
<Pressable
onPressIn={handleOnPressIn}
onPress={handleOnPressCenterAlignment}
style={tailwind.style(
"flex flex-row items-center justify-center w-[70px] h-[70px]",
)}
>
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
<View
style={tailwind.style("absolute h-2 w-6 bg-blue-100 rounded-sm")}
/>
</Pressable>
<Pressable
onPressIn={handleOnPressIn}
onPress={handleOnPressRightAlignment}
style={tailwind.style(
"flex flex-row items-center justify-end w-[70px] h-[70px]",
)}
>
<View style={tailwind.style("h-2 w-6 bg-blue-100 rounded-sm mr-1")} />
<View style={tailwind.style("h-8 w-1 bg-blue-100 rounded-xl")} />
</Pressable>
<Animated.View
style={tailwind.style("absolute flex flex-row items-center left-0")}
>
<Animated.View
style={[
tailwind.style("h-8 w-1 bg-blue-600 rounded-xl z-50"),
verticalLineStyle,
]}
/>
<Animated.View
style={[
tailwind.style("absolute h-2 w-6 bg-blue-600 rounded-sm z-50"),
horizontalLineStyle,
]}
/>
</Animated.View>
</Animated.View>
</View>
);
};

And a final interaction like this:

Final Interaction

This blog is part of the UI Interaction List, take a look at my other blogs on various other Interactions here.

UI Interactions With React Native and Reanimated

26 stories

This is Karthik from Timeless

I hope you found this blog post helpful and informative. If you have any feedback or suggestions, please leave a comment below. I’d love to hear your thoughts and opinions on the topic.

And if you have any ideas for future blog posts, please let me know! I’m constantly looking for new and interesting topics to explore.

Thank you for reading, and I look forward to hearing from you. :)

--

--