Jump to content
Search Community

How to do adaptive animation by the best way in React

yurbas test
Moderator Tag

Recommended Posts

 

Hi! I'll be very appreciated if you help me figure out how to reinitialize animation by the best way using React.

 

I have flex containers, so when i change window height, i want my animations get new values, such as 'scrub = 3' on window size more then 600px, or 'scrub = true' otherwise, because on small height  3 seconds scrub will break animation. Also some elements wants to know actual window size  for right positioning. 

You see, purple square want to move directly to the middle of next section, so we set 'y' value as section height, which exactly is window.innerHeight

tlFromBot.to('#purpleSquare', {y: window.innerHeight, rotation: 360}, 0)

But window.innerHeight is dynamic value, so what the best way for smooth reinitializing values, recreate new animations or maybe somehow change values in existing animations?

 

 

See the Pen gOdLEoB by YuriyIzyurov (@YuriyIzyurov) on CodePen

Link to comment
Share on other sites

Hi @yurbas and welcome to the GreenSock forums!

 

You might want to give GSAP MatchMedia a try for this. Is great since it leverages GSAP Context in it so you don't have to worry about creating a GSAP Context instance on top of it and you get all the benefits from GSAP Context:

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

 

Changing your effect hook in the App component to this seems to work better:

useLayoutEffect(() => {
  const mm = gsap.matchMedia();
  mm.add({
    isSmall: "(max-height: 600px)",
    isLarge: "(min-height: 601px)"
  }, (c) => {
    ScrollTrigger.defaults({ scrub: c.conditions.isLarge ? 3 : true });
    setMasterTL(gsap.timeline());
  })
  return () =>  mm.revert();
}, []);

You probably need to wrinkle a few things here and there in your other components as well, but hopefully this will get you on the right path.

 

Finally if you want to create a cleaner example that allows you to spread your components into different files, you can use some of the starter templates we have in our Stackblitz collection:

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

 

Hopefully this helps. Let us know if you have more questions.

Happy Tweening!

Link to comment
Share on other sites

Hi, @Rodrigo, thank you for response. Yes, i tried matchMedia() before, but in my case it seems brake animation. I rewrote code due to your recomendation, but when i change screen size -  new animation with new options is initialized, but seems old animation didn't killed properly and still affects new created animation. 😥

Link to comment
Share on other sites

Hi,

 

I'd be curious to know exactly how GSAP MatchMedia breaks your animations, I just don't recall any other user reporting that 🤷‍♂️ 

 

I created a super simple example of that approach and it seems to work as expected:

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

 

Here is a fork of our ScrollTrigger Stackblitz starter template:

https://stackblitz.com/edit/react-xrftvb?file=src%2FApp.js

 

Please try to create an example in Stackblitz since it's easier to follow different components split in different files.

 

Happy Tweening!

Link to comment
Share on other sites

7 hours ago, yurbas said:

Hi, @Rodrigo, thank you for response. Yes, i tried matchMedia() before, but in my case it seems brake animation. I rewrote code due to your recomendation, but when i change screen size -  new animation with new options is initialized, but seems old animation didn't killed properly and still affects new created animation. 😥

I'm super curious to see an example of gsap.matchMedia() breaking your animations. I cannot imagine how that's be the case. Can you please provide a minimal demo illustrating that?  I wonder if there's a simple explanation. You certainly shouldn't be afraid to use gsap.matchMedia() - it's perfect for handling animations on various screen sizes. 

Link to comment
Share on other sites

Ok i use other template https://stackblitz.com/edit/react-qckz8n?file=src/App.js

When you try to change viewport height in different position on page you still get smooth and correct animations, right? Because I'm totaly not, even on @Rodrigo example same issue

 

To make it clear i recorded video, where you can see how my animation is behave. 

 

Link to comment
Share on other sites

I don't have time to dig into your project right now (thanks for providing a minimal demo! 🙌) but I noticed that you're definitely making one of the common ScrollTrigger mistakes - nesting ScrollTriggers inside a timeline. 

 

Quote

Logic-wise, that can't work. When you nest an animation in a timeline, that means the playhead of the parent timeline is what controls the playhead of the child animations (they all must be synchronized otherwise it wouldn't make any sense). When you add a ScrollTrigger with scrub, you're basically saying "I want the playhead of this animation to be controlled by the scrollbar position"...you can't have both. For example, what if the parent timeline is playing forward but the user also is scrolling backwards? See the problem? It can't go forward and backward at the same time, and you wouldn't want the playhead to get out of sync with the parent timeline's. Or what if the parent timeline is paused but the user is scrolling? 
 

So definitely avoid putting ScrollTriggers on nested animations. Instead, either keep those tweens independent (don't nest them in a timeline) -OR- just apply a single ScrollTrigger to the parent timeline itself to hook the entire animation as a whole to the scroll position.

 

As for the matchMedia() thing, that is working fine for you, right? I very quickly tested and it seemed to be firing exactly as it should in your minimal demo

 

I'd recommend fixing the nested ScrollTriggers (you're creating two in setScrollTriggers() for each colored section and nesting those in a parent timeline) and if that doesn't resolve things and you're still stuck, just let us know and we'll take a look at your revised demo. I'm curious why you're nesting them all in a master timeline. 

Link to comment
Share on other sites

Yes, my general idea is set master timeline with global settings, and few nested scrollTriggers on childs with different properties (in demo its the same, but in real project not). I was expected when matchMedia() runs, it delete all nested things and recreate new animations and scrollTriggers, thank you for clarify that its bad practice.

But anyway, i tried also create separate independent scrollTriggers for each section, as in this case

https://stackblitz.com/edit/react-f3xf2n?file=src/App.js

Same effect, when i drag window, i getting broken animations. 

Maybe i should avoid to use timelines and try to config this with standalone scrollTrigger instances with onEnter, onLeave callbacks?

 

 

 

Link to comment
Share on other sites

11 hours ago, yurbas said:

I was expected when matchMedia() runs, it delete all nested things and recreate new animations and scrollTriggers, thank you for clarify that its bad practice.

I didn't understand that comment - what is a bad practice? gsap.matchMedia() does indeed clean up anything that was created inside of it when the media query doesn't match anymore. 

 

The issue I'm seeing logically with what you're doing is you're setting up two ScrollTriggered timelines that are animating the SAME elements. It looks like you're setting the start/end values so they probably don't overlap BUT (at least on large screens) you've got a scrub value which means it takes that much time for the playhead to "catch up". So for example, let's take this example...

// scrub from x: 0 -> 100 for the first 100 pixels (with a 1-second scrub)
gsap.to(el, {
  x: 100, 
  scrollTrigger: {
    start: 0, 
    end: 100,
    scrub: 1
  }
});

// after a 10px scroll gap, scrub to x: 200 for the next 100 pixels (with a 1-second scrub)
gsap.to(el, {
  x: 200, 
  scrollTrigger: {
    start: 110, 
    end: 210,
    scrub: 1
  }
});

The start/end values aren't overlapped at all (good), but they both have a 1-second scrub and they're both controlling the same property of the same element...now imagine the user scrolls 300px down quickly. Uh oh. The first animation (x: 0 -> 100) will start going and the playhead will lag behind, taking 1 full second to "catch up"...but we scrolled quickly, so maybe within 0.1 seconds we ALSO triggered the 2nd tween to start going. Now we've got BOTH tweens trying to fight for control of the same property simultaneously, going to two completely different values. 

 

Assuming linear easing (for simplicity), you might see that first tween get to x: 10 and then JUMP to x: 100 because that 2nd tween kicks in due to the fast scroll and the lagging scrub. 

 

It's not a ScrollTrigger/GSAP bug - it's just a logic thing in how you're setting things up. You could use the preventOverlaps and/or fastScrollEnd features to assist here. Here's a video explaining exactly what they do, courtesy of @Carl

 

Does that help? 

 

And yes, you can certainly use the other approach you described where you create new animations in an onEnter/onLeave. 

  • Like 1
Link to comment
Share on other sites

I think i explained my problem badly. Let me start from the  begining. 

I keep in mind overlaping problem, and as you can see in codepen example in first post, you can't just scroll fast so you can manage your scrub options. But doesn't matter, let's go back to the original question. 

What is the best and smooth way to do adaptive animations, which is spread over all components?

I post more undestandable example with visualization, which shows, that my animations is not overlaping and independent. Also on video you can see how matchMedia() works in my situation(sorry for quality, only 0,5 mb is allowed).

https://stackblitz.com/edit/react-f3xf2n?file=src/App.js

 

 

 

 

Link to comment
Share on other sites

Ah, okay I think I see what you're saying better now. I believe that is related to the fact that you're doing relative animations of the same element/properties in two different timelines and when a refresh() occurs (when scrolled down), the rendering order is such that the tlFromBot (2nd) one renders when the tlFromTop's playhead isn't at the very end, thus it records the "start" values incorrectly. One timeline is contaminating the other's "current" values. When I say "relative" here, what I mean is that from() and to() tweens both use the CURRENT values as either the start or end. Every animation has to record the start/end values on the first render so that it can quickly interpolate between them. A fromTo() tween is NOT relative because you're defining both the start and end. 

 

I believe the next release will improve the handling of this in ScrollTrigger (I can send you a beta zip if you'd like to try it), but there are several other solutions you can choose from: 

  1. Use fromTo() tweens (non-relative) in situations like this so that you can control both start and end values. 
  2. Just force the render in the proper order so that the values get recorded when the state is what you expect: 
    // immediatley after you create them, push the playhead to the end 
    tlFromTop.progress(1);
    // now that tlFromTop is at its end, it's safe to record the start values in tlFromBot:
    tlFromBot.progress(1);
    // now rewind them back to the start
    tlFromTop.progress(0);
    tlFromBot.progress(0);

Does that make more sense now? 

  • Like 1
Link to comment
Share on other sites

Yes, that what i looked for. Hardcoded start and end positions is decision. fromTo() works perfectly and never broke animation at window resize. That was simplier that i thought. Thank you very much!

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