Jump to content
GreenSock

Kovsky

React - Sharing timeline throught multiple components

Moderator Tag

Recommended Posts

Hi guys,
I am currently rebuilding my website and i am asking myself what is the bestway to share one timeline through multiple components ?
let's say i have this architecture

--Layout

----Header

----Component1

--------Component2

--------Component3

----Footer

 

And i want part of my header timeline starting after the one executed in component3 ?

 

Currently i am using context but it feels a bit strange to me 

Provider.js

const [globalTimeline, setGlobalTimeline] = useState(
    gsap.timeline({ paused: true })
) // Global Timeline for animation
<myContext.Provider
value={{
 
globalTimeline,
setGlobalTimeline: tl => setGlobalTimeline(tl),
}}
>
{children}
</myContext.Provider>

 

Layout.js

useEffect(() => {
globalTimeline.addLabel('start'')
 
globalTimeline.play()
}, [])

 

I call my context in every component and add my animation to the globalTimeline.
Thank you

 

 
Link to comment
Share on other sites

Well, if you're using the context API, any component down the tree can consume that and use the regular GSAP methods to start that particular timeline. Keep in mind that you'll be passing a reference of the timeline, so it shouldn't mean any major memory consumption.

 

Now normally when I'm facing a situation like that I use redux since is more intended for such things, meaning you can update a state property in the store and trigger the timeline to be played based on that specific update, which can be followed with a useEffect hook. One thing that has to be handed to the hooks API is that using redux is far more simple and intuitive that it used to be. Recently I worked on a menu in a Next app that, because of the design, had the menu button in a different component from the navbar and a different component from the actual menu. So the button controlled it's own animation as well as the animation of the menu using redux, and when choosing a menu element, the same state property controlled the menu animation and the button animation. I'll try to create a live sample of it tomorrow at some time, but hopefully you get the gist of it.

 

Happy Tweening!!!

  • Like 4
Link to comment
Share on other sites

Hi,

Thank you for your answer. Just to be sure to clearly understand you.  I can go in a file and do something like this

contextTimeline.addlabel("one-animation").to(.....) 
and in another one

contextTimeline.from("#anId", {opacity: 0}, "one-animation-=2")

And another thing i was asking to myself, do i have to update this context timeline with something like this, everytime i add another part of the animation (samet thing if i use redux)

setContextTimeline(contextTimeline)

 

 

Last night i went crazy and i splited my timelines, one masterlayout timeline, onemaster tl for index etc....

Here is an exemple for my layout's animations https://github.com/ptrkvsky/blog-v2/tree/master/src/animation/layout

and here where i build it. https://github.com/ptrkvsky/blog-v2/blob/master/src/components/Layout.jsx

Does it seems a good way to do it too ?

But i will consider using redux for this, i use the old version at work (3.0) it will be a good way to test those new features.

 

Thank you again.

PS : It would be so nice to get some github repos with simple websites with good practices for React/Gatsby/Next/Nuxt

Link to comment
Share on other sites

Mhh... Ok this is a bit different from the original question. I thought this was about just controlling a timeline in different components, but this is a different territory.

 

Yeah this situations can be a pain in the neck actually because, while is ideal to keep components as small/simple as possible, some times we run into this issues where we need a specific DOM element in a grand parent component with the purpose of creating an animation or doing something else.

 

In this case I wouldn't use the context API at all for such purpose and try to tailor a solution for your specific scenario. If your component tree has three levels (as you mentioned in your first post):

23 hours ago, Kovsky said:

--Layout

----Header

----Component1

--------Component2

--------Component3

----Footer

I'll assume that the entire timeline is being created and started in the Layout or Header component. What I would do is to use a method in the Layout component in order to access the DOM elements and add them to the timeline, and Forwarding Refs in Component1 to get the refs from Component2, because (as you already found out ;)) sharing things between sibling components can be a bit tricky. In the layout component it would look a bit like this:

import { useRef } from "react";
import Header from "./components/Header";
import Component1 from "./components/Component1";

const Layout = () => {
  const getChildTweens (tween) => {
    // In here run some code to add the GSAP instances
    // in the main timeline
  };
  return (<div>
    <Header getChildTweens={getChildTweens} />
    <Component1 getChildTweens={getChildTweens} />
  </div>);
};

In the header component

import { useRef, useEffect } from "react";
import gsap from "gsap";

const Header = ({ getChildTweens }) => {
  const headerTween = useRef(null);
  useEffect(() => {
    headerTween.current = gsap.to(/* Header Animation */);
    // You can add other parameters to include labels, etc.
    // Now you sent the header tween to the layout component
    getChildTweens(headerTween.current);
  }, []);
  return(/* JSX in here */);
};

In Component 1:

import { useRef, useEffect } from "react";
import Component2 from "./Component2";
import Component3 from "./Component3";

const Component1 = ({ getChildTweens }) => {
  const component3Tween = useRef(null);
  const component3Ref = useRef(null);
  useEffect(() => {
    component3Tween.current = gsap.to(/* Header Animation */);
    // You can add other parameters to include labels, etc.
    // Now you sent the header tween to the layout component
    getChildTweens(component3Tween.current);
  }, []);
  return(<div>
    <Component2 />
    <Component3 ref={component3Ref} />
  </div>);
};

Finally in Component 3

import React, { forwardRef } from "react";

const Component3 = forwardRef( ({ ref }) => {
  return(
    // Here you use the ref passed from the parent component
    // where you need it
    <div ref={ref}></div>
  );
});

That is actually the 100% React Way of doing this and is not exactly super simple and easy to follow, but not all that complicated

 

Another alternative would be to use redux (or a custom store object) to store references to each GSAP instance and access them in the layout component in order to create the main timeline in the layout component and run it there. To be completely honest I've never tried something like that, but it should work.

 

Happy Tweening!!!

  • Like 2
Link to comment
Share on other sites

Thank you with this very detailed answer !

I will go with this option.
Have a good sunday

Link to comment
Share on other sites

  • 8 months later...

Rodrigo -

 

Using this method, do you need to pass the timeline/tween up with a different function callback for each component?  I know in your example you used the same function for each component "getChildTweens", but won't it be overwritten for each timeline so you'll be left with just the final timeline from your last child component?  If not, how do you differentiate between each timline/tween that you pass up to the parent?  In my case, I'm passing up timelines, and in your example you're passing up Tweens, so maybe that's the difference since those are triggered as they come through.

Link to comment
Share on other sites

Well, the answer to this is simple enough.  You can create an ref in the parent component and push each timeline into it for use in a master timeline. 

 

If anyone runs into this please let me know and I'll boil the code down and post an example.

Link to comment
Share on other sites

53 minutes ago, Steve Giorgi said:

If anyone runs into this please let me know and I'll boil the code down and post an example.

 

We'd love to see some code! Did you ever make that ScrollTrigger demo you were wondering about?

 

  • Like 2
Link to comment
Share on other sites

@OSUblake I have not yet.  I have been under the gun with several deadlines but I'm definitely going to get around to it.  I am very thankful for the help you've provided already.  I haven't forgotten!

  • Like 2
Link to comment
Share on other sites

  • 1 year later...
On 9/27/2020 at 10:33 AM, Rodrigo said:

Well, if you're using the context API, any component down the tree can consume that and use the regular GSAP methods to start that particular timeline. Keep in mind that you'll be passing a reference of the timeline, so it shouldn't mean any major memory consumption.

 

Now normally when I'm facing a situation like that I use redux since is more intended for such things, meaning you can update a state property in the store and trigger the timeline to be played based on that specific update, which can be followed with a useEffect hook. One thing that has to be handed to the hooks API is that using redux is far more simple and intuitive that it used to be. Recently I worked on a menu in a Next app that, because of the design, had the menu button in a different component from the navbar and a different component from the actual menu. So the button controlled it's own animation as well as the animation of the menu using redux, and when choosing a menu element, the same state property controlled the menu animation and the button animation. I'll try to create a live sample of it tomorrow at some time, but hopefully you get the gist of it.

 

Happy Tweening!!!

@Rodrigo I need a master timeline for my homepage. Because the homepage itself is split into multiple components, a master timeline would be really helpful in handling how to sequence the animations. That's why I was trying to store a timeline as a global state. I went ahead with redux and tried using redux tookit to store a gsap timeline as a state like you suggested in this post but am running into the error "Too much recursion"

 

import { createSlice } from "@reduxjs/toolkit";
import gsap from "gsap";

const initialState = gsap.timeline();

const timelineSlice = createSlice({
  name: "timeline",
  initialState,
  reducers: {},
});

export default timelineSlice.reducer;

This is what my timelineSlicelooks like, any suggestions on what to do? I also saw a globalTimelinein the docs can that be of any help here?

Link to comment
Share on other sites

Hi @Ishan Shishodiya,

 

It seems that you also created a new thread for this:

Do you mind if we keep the discussion in that thread 👆 instead of extending this one? Since you're using redux the scenario is different than the one in this thread, so it'd be better to focus our attention in your other thread 👍

 

Happy Tweening!

  • Like 1
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.
×