Jump to content


Use React Transition with nested timeline and animate elements back in place in timeline

Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts



I'm fairly new to React and firsttimer with Greensock aminations.

I'm developing a timeline that is animated on the z-axis when the user scrolls through the page.


However I have some issues with getting React Transition Group to play well with nested timelines, and animating out of the current progress in the timeline and then animate back on track. I have the following issues:


How would I use React Transition Group in components to add/remove elements when scrolled by the element in the timeline.

As you can see in Box1 I tried to wrap the component in the Transition component, but then I cannot add a nested timeline to the main timeline in index.js, that is displayed as box2 with animate on scroll.


When clicking a box it opens with an animation. how would I make it animate back to small again, when clicking/scrolling. Right now it just jumps back to where it were on the timeline, before it was clicked (try to click box2).


I made a simple project. The real project is much larger, and I'm therefore really concerned about optimizing performance and reuse components etc.





For reference, this site is doing some of what I'm trying to achieve visually: https://2018.craftedbygc.com/


Thanks in advance

See the Pen by edit (@edit) on CodePen

Link to comment
Share on other sites

Hi and welcome to the GreenSock forums.


Honestly I don't think using transition group is a good fit for the type of site you have in mind. Transition group fits better in cases you need to animate elements that will be mounted/unmounted in the React app, rather than regular animations.


The best approach I can think of is to use either React's context API, redux/mobx or direct props to pass a method to all child components to add the particular timeline of each component to the master one. I would avoid doing this at all cost:

// create react class
class Box1 extends Component {}

export default Box1;

export const Timeline1 = () => {};


Mainly because there is no guarantee that the DOM element will be mounted when the file actually reaches the point of the function definition that creates and returns the timeline. Instead create the timeline inside the component (the react-way soto speak) and then use the method created in the parent component to send that animation and add it to the master timeline. Something like this:


Parent Element

class App extends Component {
  constructor (props) {
    this.masterTl =  new TimelineMax({useFrames:true, paused:true});
    this.addChildTimeline = this.addChildTimeline.bind(this);
  // method to add child timelines to the master
  addChildTimeline (childTl, position) {
    this.masterTl.add(childTl, position);
  render() {
    return <div>
      <Box1 addChild={this.addChildTimeline} />


Child Component

class Box1 extends Component {
  constructor (props) {
    this.box1 = null;
    this.tl = new TimelineLite({useFrames:true, paused:false});
  componentDidMount() {
      .to(this.box1, 200, {
        opacity: 1,
      // here you'll have to find a way to plug this duration value
      .to('.box-1', duration*2, {
        scale: 2.5,
        top: "80%",
        left: '-30%',
      }, "-=200");
    this.props.addChild(this.tl, "+=200");
  render () {
    return <div>
      <div ref={div => this.box1 = div}>
        // box1 content here


As you can see transition group is not needed at all and for this setup it will only make things more complicated.


Unfortunately I don't have time right now and for the foreseeable future to create a concrete sample of this, so I hope that this simple advice is enough to get you started.


Happy Tweening!!

  • Like 4
  • Thanks 1
Link to comment
Share on other sites

Hi @Rodrigo,

Thanks a lot for your response!


I Implemented your suggestions in the code. It makes so much more sense.


Yes, Redux might be a good idea. Maybe it will be implemented later.

I still feel it could maybe be beneficial to use React transition Group for e.g. Years component, so each year with all it's content will be rendered when needed. 


What is the preferred way to apply styles to components and tweens? Is it to TweenLite.set all styles on the element refs or inline style/adding a style const or using css/sass files? I'll also be using breakpoints on styles and animations, but I'm not sure whats the best practice on this either.


Thanks again, your response is gold

Link to comment
Share on other sites

Argh I just realized i have some issues in Safari.


When scrolling through the cards they are jumping up and down a bit. I tried to animate x and y properties instead of top and left, but it did not make any difference.


Link to comment
Share on other sites

You're welcome.


For styles I'm an old fashioned guy and use SASS in separate files. I just don't like styled components. There used to be this concept called "Separations of Concerns" which I still follow to this day. IMHO styles go with styles, pure JS goes with pure JS and React code goes with React code, but again that's just my and my two cents on the subject. If you use breakpoints for animations my advice would be to use the context API and the root component of your app to keep track of the screen size. Like that you can check the screen size when creating/running your animations on every component in the app without the need of passing props down the component tree. Of course if at some point you plan to implement redux you can use it as well to spread the screen size from a single component that listens to the window resize event.


The jumpy behaviour could be related to sub-pixel rendering but if you're using x and y that shouldn't be an issue. Perhaps the content your animating is creating the issue, maybe large images with transparency or shadows. Those things normally cause a lot of stress on the rendering engine. This is just on safari? Chrome, Firefox, Edge work OK?


Happy Tweening!!

  • Like 3
Link to comment
Share on other sites

  • 2 weeks later...

I replaced top and left with x and y again and it did indeed solve the jumpy behaviour in Safari. Don't know how i missed that in the first place.

Thanks again, very much appreciated!

  • Like 2
Link to comment
Share on other sites

Hi again,


So the stackblitz is running quite smoothly, but in the real project i now have maybe 100 cards flying by, split into 20 years. these cards contain images, video, audio etc. which is way too much for a normal device to handle. Therefore I'm still looking into some way of lazyloading elements or add/destroy dom elements on the fly.

I tried to use React Transition Group, as you can see in Years.js, to only load all content from a single year at a time, but I'm not sure how to trigger an RTG on a specific time in the timeline. I have the current time and the labels array for the years, but cannot wrap my head around how I can update a state/prop when the timeline progress is in a specific year.


I also have a weird problem when clicking the cards, autoAlpha 0 is set on the other cards and their previous alpha is set in a state. however when the card is clicked again, and the other cards appear on the timeline again, they get their old opacity set as intended but visibility is set to inherit, even on the cards that weren't visible before a card was clicked and should be given a hidden visibility along with opacity 0, which results in being able to click on cards that have yet to be available later in the timeline. This is where the centered card can be clicked even though it is not visible after another card is opened and closed again.


Link to comment
Share on other sites

You could filter the visible elements and render just those using RTG, like in this sample:




Of course that is just one element but is the same principle. When the show value in the state is toggled the element is animated in/out with the consequent mount/unmount.  Instead of managing the show value on each component's you could pass it as a prop to each element.


If you ask me I would use array.filter() to get a filtered version of the array elements that should be visible and render just those:




In that sample as soon as you remove an element, the array is reduced but the element is still rendered. As the animation-out is completed the element is removed from the App's tree. Take a look at both samples and hopefully you'll get a good grasp on how to tackle your situation.


Finally you could take a look at this article by Addy Osmani:




Happy Tweening!!

  • Like 2
Link to comment
Share on other sites

Hi @Rodrigo


Thanks for your answer!


I tried to wrap year1996 in a Transition component, and it is appearing after scrolling to a specific time in the timeline. But I'm not sure how I'm supposed to remove it again with addEndListener.


I also need to find another way to calculate the height of the document that should match the duration of the timeline, as this is unknown when the elements are not added to the dom all at once. or maybe find  another approach, that does not require the document to be as heigh as the timeline duration. for now it's just set in componentDidMount in index.js with an offset of 10000 to make room for 1996.


Whether the RTG should be on an entire year or every element is not at all decided yet. whatever works :)

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.