Khaled Rakhisi
Khaled Rakhisi

Reputation: 325

How do I apply CSS transitions (e.g. fade-ins) to a list of items in React so their animations are sequenced one after the other on render?

I've been searching Stack Overflow and the whole internet for this and couldn't find the right answer, so sorry if this is a duplicate question.

I've got a list:

enter image description here

How do I apply CSS transition to the list of elements, one by one, only on page load in React?

I was able to use ReactCSSTransitionGroup, and it works fine, but it applies the transition to the entire list at the same time.

React:

<ul className="item-list">
  <ReactCSSTransitionGroup transitionName="fade" transitionAppear={true}
    transitionAppearTimeout={2000}>
    {props.items.map((item, i) => (<li><someComponent/></li>)}
  </ReactCSSTransitionGroup>
</ul>
...

CSS:

.fade-appear{
  opacity: 0;    
}
.fade-appear-active{
  opacity: 1;
  transition: opacity 500ms ease-out;    
}

As I previously mentioned, I need to apply the above transition to the list of items one after another.

Upvotes: 2

Views: 6525

Answers (3)

Aaron Sarnat
Aaron Sarnat

Reputation: 1235

I think the effect is usually referred to as “staggered,” “cascading,” or “sequenced.”

Rather than using ReactCSSTransitionGroup, you could do this mostly with CSS.

First, I'd animate your cards using animation property and @keyframes instead of transition property. So to start, you could add something like this to your CSS:

CSS

@keyframes fadeIn {
  0% {
    opacity: 0;
  }

  100% {
    opacity: 1;
  }
}

Javascript

The crux of the solution is to set an animation CSS style on each list item, and use the item index as a multiplier for a specified delay value.

I started by creating an array of objects called items, where each object contains a title and a text field (mainly just needed an array to map for the example).

I also created a couple of constants for abstracting the two numerical values for the animation, duration and delay (note we're only doing math with delay in the example to follow, but it looked cleaner to me to pull out duration as well):

const duration = 1000; // ms
const delay = 500; // ms

Made a template that returns a formatted string to be used as the value of each transition element's animation CSS property:

const animStr = (i) => `fadeIn ${duration}ms ease-out ${delay * i}ms forwards`;

Mapping the data during render time, and setting the CSS animation style based on the index value (i) via animStr:

{items.map((item, i) => (
  <li key={i} style={{ animation: animStr(i) }}>
    <SomeComponent item={item} />
  </li>
))}

The animation will become active as soon as that element is injected into the DOM (as per the CSS animation spec). Syntax is based on the css animation shorthand. Note that the default behavior for the animation is to run once. Adding forwards to the rule causes the animation to retain the properties of the last keyframe when it stops (fully visible).


Edit: Personally, I think it looks better to start the delay index at 1 instead of 0, so you could set your animation value to this:

`fadeIn ${duration}ms ease-out ${delay * (i + 1)}ms forwards`

Working CodeSandbox

Here's a working codesandbox.


Screen Recording

This is what the above code looks like in action. It's a screen recording of the page being reloaded on CodeSandbox.

enter image description here

Upvotes: 4

Aaron Sarnat
Aaron Sarnat

Reputation: 1235

If you're committed to keeping ReactCSSTransitionGroup then you could probably just add a custom transition-delay property on each item, similar to the solution in my other answer.

const delay = 500;

And do something like this:

{props.items.map((item, i) => (
  <li style={{ transitionDelay: `${delay * i}ms` }}>
    <SomeComponent item={item} />
  </li>
)}

Upvotes: 1

Aaron Sarnat
Aaron Sarnat

Reputation: 1235

Another way to solve this would be to use a library. Both of the following libraries can achieve this effect with some added features:

react-drizzle

react-awesome-reveal

Upvotes: 1

Related Questions