Carousel: Part 1
Carousel: Part 1

Watch everything for $29/month.

Join Build UI Pro

Carousel: Part 1

Learn how to use the `x` shorthand property to smoothly animate an image container.

Video resources

Summary

To animate our image carousel, we'll start by rendering all our images in a flex container:

<div className="flex">
  {images.map((image, i) => (
    <img key={image} src={image} className="aspect-[3/2] object-cover" />
  ))}
</div>

Now that the images are all rendered side-by-side in a row, we can animate them by shifting the flex container left and right. Framer Motion provides a convenience property x which we can use to easiliy set the translateX position of a motion element.

Since each image takes up the full width, translating the container by increments of 100% will animate in each successive image:

<motion.div animate={{ x: `-${index * 100}%` }} className="flex">
  {images.map((image, i) => (
    <img key={image} src={image} className="aspect-[3/2] object-cover" />
  ))}
</motion.div>

If we look at the inspector, we'll see that the shorthand { x: "100%" } is applying the style transform: translateX(100%) to our motion element.

Notice how we're able to derive our animation entirely from our existing React state – the current index. Typically, if you're coding your Framer Motion animations correctly, you should not need to add any new state just for the sake of the animations, and instead be able to derive your animation code from your existing application logic.

Custom easing

Our animation is working, but the default spring animation is a bit aggressive. Let's customize it with the transition prop:

<motion.div
  animate={{ x: `-${index * 100}%` }}
  transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
/>

The ease property has some pre-defined values like easeOut, but it also accepts a array of four values that define a cubic bezier curve. There's lots of curves you can find around the internet, but here we're using the one from Ionic's modal enter animation which emulates an iOS transition. This curve turns out to be a perfect fit for our carousel, and adds a nice amount of character while still feeling snappy and responsive.

Avoiding conflicts with CSS transitions

We're hiding our left and right nav buttons when we're at the beginning and end of our carousel. Let's add some animation to these buttons using AnimatePresence.

<AnimatePresence initial={false}>
  {index > 0 && (
    <motion.button
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 1 }}
      className="bg-white/60 transition hover:bg-white/80"
      onClick={() => setIndex(index - 1)}
    >
      <ChevronLeftIcon />
    </motion.button>
  )}
</AnimatePresence>

Once we add this, we see some jankiness in our animation. It turns out our existing CSS transitions that are being applied with Tailwind's transition class are interfering with our Framer Motion animations.

When adding Framer Motion to animate a specific CSS property, it's usually best to remove any CSS code that is also animating or transitioning that property, so that Framer Motion has total control over the effect

Let's just set the background to pure white, and remove the background opacity and transition from our class list. We can also add back in the hover effect using Framer Motion's whileHover prop:

<AnimatePresence initial={false}>
  {index > 0 && (
    <motion.button
      initial={{ opacity: 0 }}
      animate={{ opacity: 0.7 }}
      exit={{ opacity: 1 }}
      whileHover={{ opacity: 1 }}
      className="bg-white"
      onClick={() => setIndex(index - 1)}
    >
      <ChevronLeftIcon />
    </motion.button>
  )}
</AnimatePresence>

Now there's no more flickering due to CSS transitions interfering with our Framer Motion animations.

Using MotionConfig to reduce duplication

We're currently animating two elements – our container with the x property, and our buttons with the opacity property. In this particular example it'd be ideal if these properties were all animated using the same transition, so that the fades and effects all line up.

Our carousel is shifting using a custom ease transition:

<motion.div
  animate={{ x: `-${index * 100}%` }}
  transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
/>

We could copy and paste this transition to our fading button elements:

<motion.button
  initial={{ opacity: 0 }}
  animate={{ opacity: 0.7 }}
  exit={{ opacity: 1 }}
  transition={{ duration: 0.7, ease: [0.32, 0.72, 0, 1] }}
  whileHover={{ opacity: 1 }}
  className="bg-white"
  onClick={() => setIndex(index - 1)}
>
  <ChevronLeftIcon />
</motion.button>

But this duplication could fall out of sync pretty quickly. Fortunately, Framer Motion exports a MotionConfig provider component that we can use to customize the transition for an entire section of our React tree.

To use it, let's improt MotionConfig and render it as a provider above all our common motion elements, passing in our custom transition:

import { MotionConfig, motion } from "framer-motion";

export default function Page() {
  let [index, setIndex] = useState(0);

  return (
    <MotionConfig
      transition={{
        duration: 0.7,
        ease: [0.32, 0.72, 0, 1],
      }}
    >
      <motion.div animate={{ x: `-${index * 100}%` }} className="flex">
        {/* ... */}
      </motion.div>

      <AnimatePresence>
        <motion.button
          initial={{ opacity: 0 }}
          animate={{ opacity: 0.6 }}
          exit={{ opacity: 0 }}
        >
          {/* ... */}
        </motion.button>
        )}
      </AnimatePresence>
    </MotionConfig>
  );
}

Now both our flex container and our buttons share the same custom transition from the provider. This is a great way to keep all current and future animations we add to this component perfectly in sync.

Join Build UI Pro to access this video's summary and source code.

Join Build UI Pro

Watch every video, support our work, and get exclusive perks!

Build UI is the new home for all our ideas. It will eventually have hundreds of premium videos and a thriving community, but right now it's the early days.

If you like what you see and you've ever wanted to support our work, subscribe today and start enjoying all the perks of becoming a member!

$29/month

Watch everything. Cancel anytime.

What you'll get as a Build UI Pro member

Full access to all Build UI videos

Get full access to all of our premium video content, updated monthly.

Private Discord

Ask questions and get answers from Sam, Ryan and other pro members.

Video summaries with code snippets

Easily reference videos with text summaries and copyable code snippets.

Source code

View the source code for every video, right on GitHub.

Invoices and receipts

Get reimbursed from your employer for becoming a better coder!