DEV Community

Roland Balogh
Roland Balogh

Posted on

Let's build a slider from scratch in React Native

The other day I implemented a component based on a design which was about a unique vertical slider for selecting the user's height. I started to look around for already existing solutions but nothing seemed to fit, so I created one from scratch. Hope it helps someone out there.

Slider design

TLDR

I created a repo in case you just want to spin up a test project and try things out.
Check it here

Starting point

Firstly, you need to create a React Native project. I won't go into details as there are plenty of nice articles in the topic.
Here is a link about setting up all the things

We will build the slider in the App.js file.

The building blocks

After starting a React Native project we can finally code.
Let's add the basics of our slider. The code below contains the parts of it with some basic styles. I'll run through these in a bit.

import React from 'react';
import {SafeAreaView, StyleSheet, Text, View} from 'react-native';

const App = () => {
  return (
    <SafeAreaView>
      <Text style={styles.title}>What's your height?</Text>
      <View style={styles.slider}>
        <View style={styles.rail}>
          <View style={styles.railFill} />
        </View>
        <View style={styles.stepper} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  title: {
    textAlign: 'center',
    fontSize: 20,
    marginVertical: 50,
  },
  slider: {
    width: 50,
    height: '80%',
    marginLeft: 'auto',
    marginRight: 'auto',
    position: 'relative',
    marginBottom: 50,
  },
  rail: {
    width: 20,
    height: '100%',
    marginLeft: 'auto',
    marginRight: 'auto',
    backgroundColor: '#DBDBDB',
  },
  stepper: {
    width: '100%',
    height: 5,
    backgroundColor: 'black',
  },
});

export default App;
Enter fullscreen mode Exit fullscreen mode

Slider

This View contains all of our slider content. I set the height to 80% instead of using pixels to prevent accidents in smaller phones.

Rail

The rail is the place where the stepper will actually slide up and down. It contains a View which is going to fill the rail indicating where we are in the slider.

Stepper

Grabbing the stepper we can actually move it to set a value for the slider and also indicating the status.

At this point we have a not too good looking slider which doesn't respond to anything:

Alt Text

Functionality

Let's make this slider respond to the outside world. For this we can use React Native's PanResponder and Animated API. These two APIs can work together really nicely, so let's use them.

Firstly we need to calculate our slider's dimensions.
We can use the state for this:

const [sliderDimensions, setSliderDimensions] = useState({
  height: null,
  top: null,
  bottom: null,
});
Enter fullscreen mode Exit fullscreen mode

We'll store the slider's height, top and bottom values here. The top and bottom values are the boundaries of the slider on the screen.

To get these values we need to use the onLayout function on the slider View. With this we can get the component's dimensions when mount or layout change occurs:

<View
  style={styles.slider}
  onLayout={(evt) => {
    const {height, y} = evt.nativeEvent.layout;
    setSliderDimensions({
      height: height,
      top: y,
      bottom: y + height,
    });
  }}>
  <View style={styles.rail}>
    <View style={styles.railFill} />
  </View>
  <View style={styles.stepper} />
</View>
Enter fullscreen mode Exit fullscreen mode

After we have the dimensions we create two Animated instances. One for the stepper and one for the rail fill.

const stepperAnim = useRef(new Animated.Value(0)).current;
const railFillAnim = useRef(new Animated.Value(0)).current;
Enter fullscreen mode Exit fullscreen mode
<View
  style={styles.slider}
  onLayout={(evt) => {
    const {height, y} = evt.nativeEvent.layout;
    setSliderDimensions({
      height,
      top: y,
      bottom: y + height,
    });
  }}>
  <View style={styles.rail}>
    <Animated.View style={styles.railFill} />
  </View>
  <Animated.View style={styles.stepper} />
</View>
Enter fullscreen mode Exit fullscreen mode

Last part for the functionality is the PanResponder. We can create a responder with the create method. Here we need to implement four callbacks.

const stepperResponder = PanResponder.create({
  onStartShouldSetPanResponder: () => true,
  onPanResponderGrant: () => {
    stepperAnim.setOffset(stepperAnim._value);
    railFillAnim.setOffset(railFillAnim._value);
  },
  onPanResponderMove: (evt, {dy, moveY}) => {
    if (moveY > sliderDimensions.top && moveY < sliderDimensions.bottom) {
      stepperAnim.setValue(dy);
      railFillAnim.setValue(-dy);
    }
  },
  onPanResponderRelease: () => {
    stepperAnim.flattenOffset();
    railFillAnim.flattenOffset();
  },
});
Enter fullscreen mode Exit fullscreen mode

onStartShouldSetPanResponder

With returning true the PanResponder will become active when there is a gesture on the component.

onPanResponderGrant

This callback fires when the initial gesture occurs which will be the press on the stepper. Here we need to make sure to set the current position of the stepper and the fill as offset. This is important because we want to start the next gesture from the position we stopped before.
We can achieve this with the setOffset method of the Animated API.

onPanResponderMove

Here we need to handle the dragging gesture itself. In the callback we get the event, and the gestureState objects and the second one provides us useful informations.

The dy field gives us the accumulated distance since the gesture started which means it starts from zero every time. We need to set the stepper animation value to this. If we have an offset on the stepper animation, this value will be added to that.

The moveY field gives us the vertical location of the movement on the screen. With this, and the already calculated slider dimensions we can create a condition to make the stepper move only between the slider boundaries.

onPanResponderRelease

When the user releases the stepper we need to reset the offset to zero with the flattenOffset method.

We need to attach the responder to the stepper view and use the animation value to move the stepper along the Y axis. Here is our stepper after the changes:

<Animated.View
  {...stepperResponder.panHandlers}
  style={[
    styles.stepper,
    {
      transform: [{translateY: stepperAnim}],
    },
  ]}
/>
Enter fullscreen mode Exit fullscreen mode

Finally we need to create the fill color:

// Rail View
<Animated.View style={[styles.railFill, {height: railFillAnim}]} />;

// Rail style
const styles = StyleSheet.create({
  //...
  railFill: {
    width: '100%',
    backgroundColor: '#CBAA71',
    position: 'absolute',
    bottom: 0,
  },
});
Enter fullscreen mode Exit fullscreen mode

Making things prettier

We still need to implement the stripes for our slider somehow.
For this we can fill the rail with extra Views which have a white color, same as the whole app. This will finally give us the colored stripes.

The code below maps through an array and renders a View with the height of five pixels, and a spacing of another five pixels by setting the bottom position for each.

<Animated.View style={[styles.railFill, {height: railFillAnim}]}>
  {sliderDimensions.height
    ? Array.apply(
        null,
        Array(Math.floor(sliderDimensions.height / 10)),
      ).map((item, index) => (
        <View
          key={index}
          style={[styles.railFillSpace, {bottom: index * 10}]}
        />
      ))
    : null}
</Animated.View>
Enter fullscreen mode Exit fullscreen mode

You can see the final code here.

Alt Text

This is it, the last stripe part went a little bit hacky but for now I'm happy it works.

On the other hand I hope this little article encourages you to start experimenting without using third parties. It is a good way to learn new things in a library or framework.

If you have any feedback, or opinion what you would do differently let me know in the comments below.

Oldest comments (4)

Collapse
 
mahermalhem profile image
maher malhem

hello
i am trying to build audio waves based on slider idea
value -- onValueChange and colors similar to soundCloud but more simple
so i tried to take your code into new project of mine but it didnt responded to my gestures

i need some help with your code please contact me
Regards Maher,

Collapse
 
baloghroli profile image
Roland Balogh

Hi Maher!
Can you paste a repo here so I can have a look?
I'll also update the article and include value calculations later on.

Collapse
 
thorabc profile image
Thorabc

Hi there,
I tried to make a slider following this guide and it's a good guide, but i'm not sure how to do so the stepper stays inside the slider container. My slider has a height of 287 but the stepper somtimes seem to move the whole height of the screen

Collapse
 
huanghanzhilian profile image
Jipeng Huang

Very useful, thanks for all the answers, solved the problem in my open source project

nextjs full stack: github.com/huanghanzhilian/c-shopping

react native (expo) app: github.com/huanghanzhilian/c-shopp...