Jump to content
GreenSock

omarel

Simple opacity fade doesn't work in React?

Recommended Posts

@stellar027 I would turn it off for the time being. Unless you are working on true react applications, there isn't a massive benefit in strict mode (in my opinion). Can't speak to when the new feature will be released but it will make the problem much easier to deal with.

Link to comment
Share on other sites

I wanted to note we've started adding some documentation about the mindset with which to approach these issues:

 

https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development

https://beta.reactjs.org/learn/synchronizing-with-effects#triggering-animations

 

This doesn't specifically mention GSAP and may not be particularly helpful but at least I hope this clarifies the "way of thinking" we suggest.

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

No ETA exactly, but I'll DM you with a beta version of a new feature we're working on that should be a big help in working around the double-call stuff (and also streamline several other things). 👍

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

Thanks for the additional info, @gaearon. Much appreciated. 

Link to comment
Share on other sites

  • 1 month later...

The technique that came to mind reading the description of the problem was to "remember" the calculated "target" values of the .from() tween when it gets called the first time the component is mounted and then when it gets remounted do a .fromTo() instead (under the hood) using those "remembered" "target" values

Basically that way you're forcing it to tween to the intended target values (instead of getting confused and targeting wherever the first tween had gotten to so far) when it runs again on remount

Here's a rough example that shows encapsulating that behavior in a useDoubleCallableGsapFrom() custom hook: https://github.com/helixbass/gsap-double-call-test (if you clone that repo and then from the top-level directory of the repo run yarn install and then yarn start it should launch the example app locally)

Specifically it's using this technique + gsap.getProperty() to achieve the "remembering"

And caveats: I wouldn't start using this "for real" as-is, there could be fundamental reasons this isn't a good idea, I have no idea if using gsap.getProperty() that way would work for all possible target properties, as written I think you could only use the callback "once" (ie the equivalent of a single gsap.from() call per component instance) nor would it handle if you're targeting multiple elements and they have different calculated "target" values from each other, ...

Link to comment
Share on other sites

Thanks for popping in @helixbass - we also have a solution coming in the next release which will help you all get around this.

Link to comment
Share on other sites

@Cassie now that I've started trying to think my way through the problem I'm pretty curious what the basic mechanics/technique of a "GSAP-internal" solution would be. But no problem if that doesn't belong in this thread (or if I'm not "privy" to it)

  • Like 1
Link to comment
Share on other sites

  • 3 weeks later...

Hi all,

I've been working on a tool to integrate React with libraries like this one, with the aim of managing internally all the complexity and hidden pitfalls (especially with React 18). 

This work started because I needed gsap in my React app, and in fact, I used it for the examples in the documentation.

 

Those interested can find the examples with gsap and gsap/draggable, with the codesandbox links. The project can be found on Github.

I released this tool today, the documentation and testing should be quite solid but it should be treated as a pre-release.

Feel free to open a discussion if you want to try it out and need help.

 

Cheers

  • Thanks 2
Link to comment
Share on other sites

The new gsap.context() in GSAP 3.11 totally solves this issue - it offers two key benefits:

  1. Collects all GSAP animations and ScrollTriggers that are created within the supplied function so that you can easily revert() or kill() ALL of them at once. No need to keep track of a bunch of variables, Arrays, etc. This is particularly useful in React modules or anywhere you need to be able to "clean up" by reverting elements to their original state.
  2. [optionally] Scopes all selector text to a particular Element or Ref. This can help simplify your code quite a bit and avoid needing to create lots of Refs in React/Angular. Any GSAP-related selector text inside the supplied function will only apply to descendants of the Element/Ref.

Let's say you've got a big block of GSAP code that's creating a bunch of different animations and you need to be able to revert() them all...

let ctx = gsap.context(() => {
 gsap.to(...);
 gsap.from(...);
 gsap.timeline().to(...).to(...);
 ...
});

// then later...
ctx.revert(); // BOOM! Every GSAP animation created in that function gets reverted!

 

See the Pen xxWRjyq by GreenSock (@GreenSock) on CodePen

 

Docs: https://greensock.com/docs/v3/GSAP/gsap.context()

  • Like 3
Link to comment
Share on other sites

9 hours ago, GreenSock said:

The new gsap.context() in GSAP 3.11 totally solves this issue.

Thank you!

I like the general concept of contexts, but I don't think I fully understand some things.

 

revert() applied to a Tween should reset its initial CSS, but the docs says that revert() applied to a context also kill() its Tweens: 

Quote

When revert() is called on a Context, it is permanent for the animations/ScrollTriggers it contained. They get reverted and killed and the Context clears itself out, making things eligible for garbage collection.

The implicit kill() seemed confusing to me. I searched into the source code and did some tests, and it seems that context.revert() does Not destroy the animations.

 

I would definitely prefer this behavior, but if my tests are correct, the demo you linked (and the one in the docs) might have a memory leak since kill() is never called.

 

AFAIU, the context is just an utility for managing groups of animations, and the only mandatory part for the React integration is to kill() the animations inside the effects cleanup function (eventually revert().kill() if the element is reused).

 

What I don't understand is why the context function also accepts a React ref. From the source code it looks like just a shortcut for ref.current, but it doesn't seem right that GSAP is somehow aware of some React specific APIs.

 

I hope this is the right thread to discuss this, and by the way thank you all for the fantastic work you are doing with gsap!

 

Link to comment
Share on other sites

6 hours ago, paol-imi said:

The implicit kill() seemed confusing to me. I searched into the source code and did some tests, and it seems that context.revert() does Not destroy the animations.

 

I would definitely prefer this behavior, but if my tests are correct, the demo you linked (and the one in the docs) might have a memory leak since kill() is never called.

Are you saying you actually think there is a memory leak (and see evidence?) or you're just guessing that because you don't see an explicit kill() called? When you revert() an animation, it removes it from its parent, thus it is disconnected and available for garbage collection. It doesn't need an explicit kill() call. 

 

6 hours ago, paol-imi said:

What I don't understand is why the context function also accepts a React ref. From the source code it looks like just a shortcut for ref.current, but it doesn't seem right that GSAP is somehow aware of some React specific APIs.

It's a convenience and it has no dependency whatsoever on React itself. GSAP is fully framework-agnostic. But we recognize that a TON of people use React and Angular, so our goal was to make it easier. We try to solve real-world problems with our tools. This is very much a real-world problem/solution. React developers use refs all over the place, so we wanted to deliver a simpler way of interacting with GSAP using refs, at least with scoping. It allows you to significantly reduce your code. Let's say you've got an <h1> and three elements with a ".class" class: 

 

const component = useRef(null); 
const h1Ref = useRef();
const classEl_1 = useRef();
const classEl_2 = useRef();
const classEl_3 = useRef();

useEffect(() => {

  const tween1 = gsap.from(h1Ref.current, {...});
  const tween2 = gsap.to([classEl_1.current, classEl_2.current, classEl_3.current], {...});
    
                     
  return () => { // cleanup! 
    tween1.revert();
    tween2.revert();
  }
}, []);

You'd have to assign a ref to every one of them, and then reference the ".current" for each one, etc. But with the scoping, you can use one ref for the component and then selector text for the rest, and cleanup is easier too: 

 

const component = useRef(null); // we only need a ref for the root-level element of this component so we can use selector text for everything else.

useEffect(() => {

  let ctx = gsap.context(() => {
    gsap.from("h1", {...});
    gsap.to(".class", {...});  
  }, component); // <- scopes all selector text inside the context to this component

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

Does that clear things up? 

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

Yes, this clarifies all my doubts. Thanks for the detailed answer.

  • Like 1
Link to comment
Share on other sites

I'm reading up on gsap.context and wondering if it fits my use case.

 

We have a bunch of JS modules that import gsap from 'gsap' and make animations. My assumption is that, by doing this, all our modules are using GSAP's global timeline.

 

I'd like to decouple this, so each module uses its own timeline. The primary goal is easier cleanup, especially for gsap.delayedCall that may run after a module has been destroyed, causing exceptions.

 

The docs for gsap.context state:

Quote

A Context is not meant to serve as a way of controlling animations. That's what Timelines are for. A Context is simply for reverting/killing and for [optionally] defining a scope for selector text.

 

So I haven't yet figured out how to combine the two, or whether that's the right course of action.

 

For example, instead of import gsap from 'gsap', we could wrap our modules they're already provided a GSAP reference (would this be a timeline? a context?). The aim would be some simple timeline.killAll() or context.revert() when destroying our modules.

 

Link to comment
Share on other sites

Heya!

So Timelines and Context solve completely different use cases. Timelines are for visually structuring and controlling sequences of animations. They're the backbone of what makes GSAP so powerful in terms of creating animations. An integral part of animation structuring. If you want to play some animations in a specific order - that's a timeline's job.

Context is purely for cleanup. (and as that post says, a helpful way of providing scope)

Neither of these things have anything to do with imports. If your goal is just to call revert then you're after context.

Here's a demo, maybe it helps clear things up? We've got a timeline that's playing some animations in order and looping them infinitely. We've also got another tween that's not associated with that timeline. That's looping by itself.  The context is grouping them all and allowing us to revert them all in one go.

See the Pen XWEveMw?editors=0011 by GreenSock (@GreenSock) on CodePen



Does this help?

  • Like 1
Link to comment
Share on other sites

Firstly thank you Greensock for taking initiative on this!  Just a question regarding this new feature to solve the  double render issues:

 

Is there any difference between the new context and @Cassie original solution in this thread to use immediate render false and .kill() on the animations. I love that we now have a more specific way to solve for this but just wondering if there’s any more differences with using .context() and .kill()?  Because I notice with context you have to wrap all your gsap code in it whereas with .kill() you can just return it in the useEffect hook itself directly. 

Link to comment
Share on other sites

Hey there! So TLDR the fix I suggested actually isn't a fix. It works, but it isn't actually following React's guidance to 'reset all elements back to their pre-animation state'

 

Here's a demo showing the difference between revert and kill (check out the inline styles in dev tools too)

See the Pen JjLgPBK?editors=1010 by GreenSock (@GreenSock) on CodePen



Context is using revert() under the hood. You can just call revert() on each tween or timeline, but if you have a bunch of tweens, timelines and scrollTriggers it's easier to wrap them all in a context function and revert() them as a whole.

  • Like 2
Link to comment
Share on other sites

On 8/29/2022 at 9:33 PM, timiyay said:

I'd like to decouple this, so each module uses its own timeline. The primary goal is easier cleanup, especially for gsap.delayedCall that may run after a module has been destroyed, causing exceptions.

The current version doesn't get rid of delayedCalls when you revert() a context, but I think it makes sense to do that so I've added it in the next release which you can preview at https://assets.codepen.io/16327/gsap-latest-beta.min.js

 

I don't think you need to worry about creating a new timeline for each of your components if your goal is just to revert() things. If, on the other hand, you want to be able to control the entire sequence like .pause(), seek(), resume(), etc., that's the perfect spot for a timeline. 

 

Cassie is exactly right about the old .kill() idea not being a true, robust "solution". This is why we created gsap.context() - it's PERFECT for solving these issues in React and simplifying your code (including cleanup). 

Link to comment
Share on other sites

Here's a video to clear it all up in case anyone's struggling! Happy tweening pals.
  • Like 3
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.
×