2

A Story about React and Input Ranges

 3 years ago
source link: https://blog.bitsrc.io/a-story-about-react-and-input-ranges-70f1a941d173
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

A Story about React and Input Ranges

How my ideas evolve over time

Picture of an input range slider
Picture of an input range slider
Image of a range slider input

People often talk about ideas as something that happens in an instant. Like they’re Eureka moments. In marketing, people reduce them to single events in time. It makes, of course, for a better story. In reality, the best ideas are almost always the product of a longer thought process. Concepts need to grow like seeds. Someone needs to tend to and nurture them. In the end, the best ideas are usually not one idea. They are often a series of smaller innovations, later on, referred to as one idea.

This article is a story about my creative process. It’s about how one input range evolved into three. I work, with my personal projects, in a way that might seem a bit disorganized. If I find something new, I might pause my ongoing extracurricular activity to work on other ideas. After working on other things, I may or may not return to the project. It all depends on where my inspiration takes me. However, if I go back, it’s always with new insights and fresh ideas on how to move the project forward.

Naturally, one cannot have too many projects running at the same time. Then again, not all concepts are great. Some are better off as thoughts rather than implemented. There’s a balance between quality and quantity. It is often a good idea to let ideas lay dormant for a while. Letting things soak for some time often do wonders for the creative process. Sometimes when you come back, you realize that your idea was not that good, after all. Regardless of how ideas spring to life, they mature over time.

Everyone has their formula, a routine that works best for their generic blueprint. This article is a glimpse into my thought process. Let’s dig in.

Some background

The whole thing started one day when I needed a change in direction. I had begun to run a little short on inspiration. Sometimes you need to stir up things a bit. Putting yourself in new contexts is a great way to force yourself to develop new creative ideas. At that moment, I felt like doing something with a front-end framework. Most of my explorations come in “vanilla” Javascript, HTML, and CSS. I like the fact that with plain HTML & Co, you can start iterating in seconds.

I don’t have a strong opinion when it comes to frameworks. I’ve worked with a few, and I find that most of them come from fundamentally good ideas. I had worked with React and react-spring at work, and I was intrigued by the react-spring library. There and then, I thought having a look at React and animations could be an exciting new project. Besides, I was also curious how to set React up and get it running in an environment like Codepen.

Frameworks usually have a bunch of dependencies on other js-libraries and tools. They need to be set up and configured before you can start coding. It’s not plug-and-play, in contrast to working with HTML, JS, and CSS, where you’re immediately good to go.

React-spring quickly led me to react-use-gesture, a gesture library built on React. The great thing about react-use-gesture is that it comes with a set of brilliant demos. I found, in particular, the one below to be informative. It taught me a lot about how the library works and its possibilities. It took me a while to decipher it. However, once I got the hang of it, I was ready to start my experiments.

React use gesture demo

Now that I had decided on direction and technology, I just needed something to implement. For inspiration, I sometimes turn to Dribbble. There I found the below demo.

This demo immediately got me thinking about input ranges and velocity-dependent animations. Changing the appearance depending on the velocity of moving objects is both playful and fun. It’s something that catches the viewer’s attention. In the first demo, I decided to create an effect where the knob of a slider transforms when being dragged.

1st range

Let’s have a look at the result and work our way backward. Here’s what the first range turned into:

Codepen demo of a morphing input range

Recreating the morphing knob can be broken down into the following two sub-problems. 1. Creating a velocity-dependent animation and 2. morphing the knob. The good thing about using the above-discussed libraries is that they help us with both these problems. The useDrag hook of the react-use-gesture library returns the drag velocity, which we can use to alter the animated object’s looks. The useSpring library comes with ready support for morphing SVGs. These are the two essential building blocks for creating the pen above.

The first thing to do when creating any animation with react-spring is to initiate it. A call to the useSpring hook lets us initiate the properties to animate. Secondly, it returns the set function, with which we can update the animation values later on.

const [{ x, velocity }, set] = useSpring(() => ({
x: -30,
velocity: 0,
}));

Next up is combining the useSpring hook with the useDrag hook. These two together work in harmony when combined. If/when the dragging position changes, the useSpring hook, in turn, triggers an animation. Below’s what calling the useDrag function of the demo looks like. Note how we use the set function to communicate changes back to the useSpring hook. Pay close attention to the velocity argument, passed on to the useSpring hook. These lines of code enable changing the animation depending on the speed of the dragging interaction.

const bind = useDrag(
({ movement: [x], velocity, down, direction: [dx] }) => {
if (!down) {
set({ x, velocity: 0 });
} else {
set({ x, velocity: velocity * dx });
}
},
{
initial: () => [x.get(), 0],
bounds: { left: -30, right: 150, top: 0, bottom: 0 },
rubberband: false,
}
);

A couple of other things to pay attention to:

  • The animation is direction-aware through the dx argument.
  • The down argument lets us control whether to enable or disable the animation. If the user lifts their finger, we set the velocity to 0 to end the effect.
  • The initial lambda function is needed to restart dragging from another position. Read more about why in the react-use-gesture documentation.
  • The bounds keep the handle inside the range.
  • Rubberbanding is fun but a bit too much for a range input.

Next up is binding the view and the controllers (the hooks). I.e., connecting the React hooks to the view element visible on the screen. In this case, this element is the input range handle, which we want to make interactive. The bind function is pretty clever. You can pass it on to any React component to make it draggable. Here, we pass it on to the knob. To activate the animation, we also need to wrap the JSX-element with the “animated” prefix. Here’s the knob, the enclosing element, in code:

<animated.svg
style={{ x, cursor: "pointer" }}
{...bind()}
viewBox="0 0 80 40"
height="40"
width="80"
>
...
</animated.svg>

Let’s now break down the flow of events thus far. Here’s how the whole thing works:

  • The view will, through the bind function, forward drag interactions to the react-use-gesture hook.
  • The react-use-gesture hook will, depending on different criteria, call set with different values. Examples of different scenarios are, for instance, the user starting or ending dragging, in what direction the user is dragging, etc. Calling set triggers changing the properties' value inside useSpring
  • With useSpring, we then interpolate these properties by calling the to function on each property. This call transforms the useSpring properties to CSS or SVG values. The values we’re looking at here are the x and velocity variables. These two correspond to the distance in pixel and the speed at which the user is dragging the knob.
  • These values can finally be presented on the screen, like JSX elements passed to React. Like for instance, the knob.

This cycle of events creates a feedback loop, where drag events are passed back and forth to the view. The result is the velocity-dependent animation we were looking to develop.

The last piece of the puzzle is the morphing. Below is the code that creates the shape-shifting effect. There are three keyframes in the below code. One shape corresponds to the case where the knob is not moving (velocity equals zero). The knob then has the form of the initial circular black knob. The other two are the extremes where the knob has a shape like a comet. These two are the same shape mirrored to reflect dragging in either direction. I use Inkscape to draw these vector shapes.

The boundary values 0.2 and -0.2 come from trimming the animation. The last step of working with animations is usually to find the right values/timing iteratively. The three SVG paths have the same amount of nodes and order of the nodes making up the path. Having the same number here is essential as the shape-shifting won’t look right otherwise.

<animated.path
d={
velocity &&
velocity.to({
range: [-0.2, 0, 0.2],
output: [
"M 80,20 C ...",
"M 50,20 C ...",
"M 54,20 C ...",
],
extrapolate: "clamp",
})
}
/>

2nd range

The first range is playful but not always useful. For input ranges, you often want fine-grained control. Of course, for some cases, it’s fine without, but it’s generally better UX to let users know the exact value at all times while operating the input.

Now for the essential part. With the assets created thus far, it’s a lot easier to innovate. There is no need to build something from scratch to test out new hypotheses. As we have assets and a clear direction already, we can work with what we’ve got. It’s a lot easier to develop ideas when working from something substantial than imagining all parts while shaping an idea. Furthermore, having the project rest for a while gave me new fresh ideas and a new direction to make the next version more fun and playful.

Let’s again have a look at the result and work our way backward. Here’s what the second version of the input range looks like:

2nd Codepen demo of a morphing input range

Even though they appear quite different, the second range is technically very similar to the first one. Instead of the morphing animation going horizontally, it now works vertically.

<animated.path
d={morph.to({
range: [0, 1],
output: [
"M 33,25.001 C 33,29.419278 ... Z",
"M 45,20 C 45,31.045695 ... Z",
],
})}
/>

Another thing to pay attention to is that the morphing effect is no longer velocity-dependent. It instead starts when the user begins a drag session. For this pen, there’s a separate useSpring property morph that activates/deactivates the animation. The react-use-gesture library useDrag hook controls the property. It’s down argument sets the morph variable to either 0 (inactive) or 1 (active). When the input range is idle, the handle still takes the shape of a filled black circle. The active state, however, now looks more like a guitar pick, a triangle with rounded corners. A vertical translation transform is also applied to the knob/indicator to move it upwards a bit. It makes the value pop up from behind the finger if it covers the value. This is a quite common UX pattern for touch.

There is another noteworthy difference between the two versions. That’s the addition of animated text to display the value the user is configuring. To animate the input value, react-spring, again, helps out. It comes with ready support to animate text. Here is what it looks like to animate the value:

<animated.div style={countStyle}>
{x.to((x) => Math.round((x + 23) / 1.97))}
</animated.div>

The code above is pretty straightforward. Besides using the x value to shape other parts of the animation, we print the value too. The expression above converts the movement in pixels to a value between 0 and 100. The magic numbers 23 and 1.97 are there to covert the translation to percent.

3rd range

The third version is more or less a combination of the two previous versions. It combines many of the techniques used in the two precis demos.

Again, with a bit of perspective to the two previous versions, I could come up with new ideas on how to move the project forward. One day on Twitter, I saw an animation where an object tilted a bit while moving. That immediately got me thinking of changing the input range to make it even more playful. Connecting the tilt angle to the velocity is now an easy thing to do. As I’ve already played around with velocity-dependent animations, I’ve learned a lot about how they work. Changing the angle depending on how quickly the knob moves is now merely a matter of connecting the velocity-dependent logic from the first demo to a rotate transform. To top it off, I also changed the morphing to a more unconventional shape. Here’s the result:

3rd Codepen demo of a morphing input range

To get the pear to tilt, I connect the velocity property to a CSS rotate transform. In code:

const handleStyle = {
cursor: "pointer",
height: 50,
position: "relative",
width: 50,
x,
transform: velocity.to((v) => `rotate(${-v * 80}deg)`),
};

The morphing in the 3rd range is pretty much the same as in the 2nd demo. The technique behind it is almost identical.

Final words

That’s it! Thanks for reading. I hope you feel inspired to drive your ideas forward. Let them go through stages to give them a chance to evolve. I know I do. The reason is simple: it’s fun!

Cheers


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK