Jump to content
Search Community

react context timeline

sunrisejoz test
Moderator Tag

Recommended Posts

so, i'll start by stating im not the greatest React ninja out there. so much so, that i've had to read up a fair bit on useContext, useLayoutEffect, useRef etc.

 

i've followed a couple of your examples along and nearly have something "working". It looks like i've fell over with the timeline (across components) not using the globalTimeLine that i set in the root el.

 

while the animations do work, they all (components) seem to be singing their own song; running to their own version of a timeline. any tips, assistance, spare brain cells welcomed here https://codesandbox.io/s/elegant-wiles-qyt2jt?file=/src/components/Copy.js&resolutionWidth=320&resolutionHeight=675

Edited by sunrisejoz
typo
Link to comment
Share on other sites

Hi @sunrisejoz and welcome to the GSAP forums!

 

I think in this cases is better to create a single timeline in a parent component and either pass the timeline to it's child components in order to add instances to it, use a callback to populate the timeline or use forwardRef in order to bring refs of your child components into a parent one and create the timeline there:

https://react.dev/reference/react/forwardRef#forwardref

 

Those different approaches are explored in our Advanced Techniques article:

https://gsap.com/resources/react-advanced

 

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

  • 2 weeks later...

indeed i did @Cassie

i've setState inside useLayoutEffect like so...

 

useLayoutEffect(() => {
    let ctx = gsap.context(() => {
      // all your animations go in here...
      let tl = gsap.timeline({ duration: 2.5, delay: 1, repeat: 99 });
      // tl.set("#logo", { opacity: 1 });
      tl.set(".bg", { opacity: 2 }, 0);
      tl.to(".triangle", { right: 0, duration: 0.6, ease: "power1.in" }, 0);
      tl.to(".arrow-left", { right: 100, duration: 0.4, opacity: 1 }, 0.4);
      tl.to(".arrow-right", { right: 30, duration: 0.4, opacity: 1 }, "<");
      tl.to(".title-1", { xPercent: 100, duration: 0.6, ease: "power1.in" }, 0);
      tl.to(".title-2", { opacity: 1, duration: 0.6, ease: "power1.in" }, 1);
      // tl.to(".cta", { opacity: 1, ease: "power1.in" }, 1.8);
      setTimeline(tl);
    }, root); // <- scopes all selector text to the root element
    setTimeline(ctx);

    return () => ctx.revert();
  }, []);

then passed the value={timeline} to my provider.

 

inside the child component i am either trying to access the (animation) timeline OR add to the timeline. in any case i end up with this (logged)

 

Context {data: Array(7), _r: Array(0), isReverted: false, id: 1, selector: ƒ, …}

data: (7) [Timeline, Tween, Tween, Tween, Tween, Tween, Tween]id: 1isReverted: falselast: ƒ f()selector: ƒ (v)_r: [][[Prototype]]: Object 'ctx'

Link to comment
Share on other sites

Hi,

 

The reason for the unexpected log in your console is because you're calling your setTimeline() method twice: Once with the GSAP Timeline you're creating as a parameter and a second time with the GSAP Context instance you're creating as a parameter.

useLayoutEffect(() => {
  let ctx = gsap.context(() => {
    // all your animations go in here...
    let tl = gsap.timeline({ duration: 2.5, delay: 1, repeat: 99 });
    // tl.set("#logo", { opacity: 1 });
    tl.set(".bg", { opacity: 2 }, 0);
    tl.to(".triangle", { right: 0, duration: 0.6, ease: "power1.in" }, 0);
    tl.to(".arrow-left", { right: 100, duration: 0.4, opacity: 1 }, 0.4);
    tl.to(".arrow-right", { right: 30, duration: 0.4, opacity: 1 }, "<");
    tl.to(".title-1", { xPercent: 100, duration: 0.6, ease: "power1.in" }, 0);
    tl.to(".title-2", { opacity: 1, duration: 0.6, ease: "power1.in" }, 1);
    // tl.to(".cta", { opacity: 1, ease: "power1.in" }, 1.8);
    setTimeline(tl);
  }, root); // <- scopes all selector text to the root element
  // calling setTimeline again here!!!
  setTimeline(ctx);

  return () => ctx.revert();
}, []);

So the log you're getting in your console is the GSAP Context instance. Honestly there is no need to call that method twice:

useLayoutEffect(() => {
  let ctx = gsap.context(() => {
    // all your animations go in here...
    let tl = gsap.timeline({ duration: 2.5, delay: 1, repeat: 99 });
    // tl.set("#logo", { opacity: 1 });
    tl.set(".bg", { opacity: 2 }, 0);
    tl.to(".triangle", { right: 0, duration: 0.6, ease: "power1.in" }, 0);
    tl.to(".arrow-left", { right: 100, duration: 0.4, opacity: 1 }, 0.4);
    tl.to(".arrow-right", { right: 30, duration: 0.4, opacity: 1 }, "<");
    tl.to(".title-1", { xPercent: 100, duration: 0.6, ease: "power1.in" }, 0);
    tl.to(".title-2", { opacity: 1, duration: 0.6, ease: "power1.in" }, 1);
    // tl.to(".cta", { opacity: 1, ease: "power1.in" }, 1.8);
    setTimeline(tl);
  }, root); // <- scopes all selector text to the root element

  return () => ctx.revert();
}, []);

Hopefully this helps.

Happy Tweening!

Link to comment
Share on other sites

thanks for getting back @Rodrigo i'd already omitted that, guess it didnt update on the url. i cant get that method to work and believe that callback may be a better idea for my use now.

 

i have a parent and component working just dandy, though where i have two html elements referenced, it seems to fall over.

 

const Copy = ({ addAnimation, index, copy }) => {
  const el = useRef();
  useLayoutEffect(() => {
    console.log("copy effect");
    const ctx = gsap.context(() => {
      const animation = gsap.tl
        .to("h1", { xPercent: 100, duration: 0.6, ease: "power1.in" }, 0)
        .to("h2", { opacity: 1, duration: 0.6, ease: "power1.in" }, 1);
      addAnimation(animation, index);
    });

    return () => ctx.revert();
  }, [addAnimation, index]);
  return (
    <CopyWrapper
      ref={el}
      className="copy"
      onClick={(e) => onInlineEditClick(copy.ids, e)}
    >
      <Limiter maxHeight={119}>{parse(copy?.value || "")}</Limiter>
    </CopyWrapper>
  );
};

export { Copy };

in this instance, how would i manage two (or more) elements that sit within the component? call a useLayoutEffect for each maybe??

 
Link to comment
Share on other sites

Hi,

 

Is really hard for us to debug without a minimal demo. We have a collection of Starter Templates in Stackblitz:

https://stackblitz.com/@GreenSockLearning/collections/gsap-react-starters

 

The only thing I can see not going right in your code is this:

useLayoutEffect(() => {
  console.log("copy effect");
  const ctx = gsap.context(() => {
    const animation = gsap.tl
    .to("h1", { xPercent: 100, duration: 0.6, ease: "power1.in" }, 0)
    .to("h2", { opacity: 1, duration: 0.6, ease: "power1.in" }, 1);
    addAnimation(animation, index);
  });

  return () => ctx.revert();
}, [addAnimation, index]);

The problem I see is that you are adding a new timeline to the parent timeline using the addAnimation method. Now what happens when you change the index prop? Your entire effect hook runs again and that particular GSAP Context is reverted, then a new animation is added to the parent timeline, but the parent timeline already has a reference to a timeline that no longer exists. This could create some logic issues, breaking errors and most definitely a memory leak. If the index prop is not changing then there is no need to have it as a dependency in your array. If ESLINT is complaining about it, just disable the rule in that particular line.

 

Happy Tweening!

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.
×
×
  • Create New...