# Cool Horizontal Scroll in React: Build Interactions Easily

[Konstantin Lebedev](https://www.strv.com/blog/authors/konstantin) Frontend Engineer

---

## Time for a tutorial.

**The task? Creating a fun scroll animation in which items “flip” in the direction of the scroll.** We’re going to use [react-spring](http://react-spring.surge.sh/?ref=strv.ghost.io) for animating and [react-use-gesture](https://github.com/react-spring/react-use-gesture?ref=strv.ghost.io) to tie animation to the scroll events. The native `onScroll` event handler won't do in this case, because we'll need additional information about scrolling that native `onScroll` handler doesn't provide: scroll delta in pixels, and whether the scrolling is in progress or not.

**This is what we’re going to build:**

## BASIC SETUP

We’ll start with the basic React component you can see below. The component renders a list of images from the `public` folder, and sets them as background for `div` elements:

```jsx
const movies = [
"/breaking-bad.webp",
"/the-leftovers.jpg",
"/game-of-thrones.jpg",
"/true-detective.jpg",
"/walking-dead.jpg"
];

const App = () => {
  return (
    <>
      <div className="container">
        {movies.map(src => (
          <div
            key={src}
            className="card"
            style={{
              backgroundImage: `url(${src})`
            }}
          />
        ))}
      </div>
    </>
  );
};
```

Next, we’ll apply some styling. We need to make sure that the container takes up 100% of the width, and that it allows its children to overflow:

```css
::-webkit-scrollbar {
  width: 0px;
}

.container {
  display: flex;
  overflow-x: scroll;
  width: 100%;
}

.card {
  flex-shrink: 0;
  width: 300px;
  height: 200px;
  border-radius: 10px;
  margin-left: 10px;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center center;
}
```

With the basic styling, our component will look like this:

## ADDING ANIMATION

Let’s start by adding a rotation animation. First, we’ll replace `div` element with `animated.div`. `animated` is a decorator that extends native elements to receive animated values. Every HTML and SVG element has an `animated` counterpart that we have to use if we intend to animate that element.

Next, we’ll use `useSpring` hook from react-spring package to create a basic animation that will run when the component is mounted. Eventually, we'll bind our animation to the scroll event, but for now, it’s easier to see the result if the animation simply runs on mount.

`useSpring` takes an object with CSS properties that should be animated. These properties should be set to **end values** of the animation, so if we want to rotate `div`s from 0 to 25 degrees, we set the `transform` value to `rotateY(25deg)`. To set the **initial values**, we use the `from` property which itself takes an object with CSS properties.

`useSpring` returns a `style` object that we need to set on the target component. The updated code:

```jsx
import { animated, useSpring } from "react-spring";

const style = useSpring({
  from: {
    transform: "rotateY(0deg)"
  },
  transform: "rotateY(25deg)"
});

<animated.div style={style}>
  ...
</animated.div>
```

This animation looks flat because by default the rotation is 2-dimensional; it’s rendered as if there was no distance between the observer and the rotation plane. `perspective` transformation allows us to move the observation point away from the rotation plane, making the 2-dimensional animation look 3-dimensional:

```jsx
const style = useSpring({
  transform: "perspective(500px) rotateY(0deg)"
  transform: "perspective(500px) rotateY(25deg)"
});
```

Finally, we need to add vertical padding to the container `div` to make sure that children elements don't get cut off:

```css
.container {
  width: 100%;
  padding: 20px 0;
}
```

Before we start working with scroll events, we need to make a small change to how we use `useSpring`. There are two things to keep in mind:

- we need to be able to trigger animation manually;
- we no longer need to run animation on mount.

To address both, we’ll use a different `useSpring` signature — instead of **passing an object** with CSS properties, we'll **pass a function** that returns such an object. Previously, `useSpring` returned a `style` object. With the new signature, it will return a tuple, where the first element is a `style` object, and the second is a `set` function to trigger animations:

```jsx
const [style, set] = useSpring(() => ({
  transform: "perspective(500px) rotateY(0deg)"
}));
```

Now, import `useScroll` from react-use-gesture and bind it to the container `div`. Handling scroll events is simple: if `event.scrolling === true`, rotate cards by degrees equal to scroll delta on Y-axis (`event.delta[0]`); if scrolling stops, reset rotation to `0`:

```jsx
import { useScroll } from "react-use-gesture";

const [style, set] = useSpring(() => ({
  transform: "perspective(500px) rotateY(0deg)"
}));

const bind = useScroll(event => {
  set({
    transform: `perspective(500px) rotateY(${
      event.scrolling ? event.delta[0] : 0
    }deg)`
  });
});

<div className="container" {...bind()}>
```

Animation works, but there's an undesired side effect — if you scroll sharply, the Y delta can be large, causing cards to flip more than 90 degrees. Testing different values, it looks best if cards flip no more than 30 degrees. Let's write a helper to clamp the delta value:

```jsx
const clamp = (value: number, clampAt: number = 30) => {
  if (value > 0) {
    return value > clampAt ? clampAt : value;
  } else {
    return value < -clampAt ? -clampAt : value;
  }
};
```

Use this in `useScroll`:

```jsx
const bind = useScroll(event => {
  set({
    transform: `perspective(500px) rotateY(${
      event.scrolling ? clamp(event.delta[0]) : 0
    }deg)`
  });
});
```

You can find a complete working demo of this interaction [here](https://codesandbox.io/s/react-spring-fun-scroll-vmncd?ref=strv.ghost.io).

---

*PS: I also made the same interaction using [framer-motion](https://framer.com/motion/?ref=strv.ghost.io). Working demo is available [here](https://codesandbox.io/s/framer-motion-fun-scroll-788qb?ref=strv.ghost.io).*

## FINAL THOUGHTS

I would like to mention two decisions that stayed behind the curtain of this tutorial but had been made before making this particular animation.

The first concern performance. To make the flip animation, we animated only `transform`, which is one of the only two properties accelerated by GPU and not taking time off the main thread (the other is `opacity`). There's quite a lot we can achieve by animating only `transform` and `opacity`, and whenever possible, we should avoid animating other CSS properties.

Secondly, responsiveness. The horizontal scroll we implemented works well on phones and tablets, but for larger desktop screens, we might want to switch to a more common grid layout. With small CSS changes and a media query, we can switch from `flex` to `grid` layout. The animation continues to work on small screens with `flex`, and is ignored on large screens where `grid` layout removes horizontal scroll.
