Jump to content
Search Community

Match Media in React

Avenues test
Moderator Tag

Go to solution Solved by Cassie,

Recommended Posts

Hi! 

 

I am working on a React Project and am having a lot of issues utilizing Match Media. Everything works great without Match Media but as soon as I use Match Media on one of my components, all other components that utilize Greensock on that same page break once the defined breakpoint in Match Media is reached.  In attempting to make a minimal demo of the issue, I am having a hard time even getting match media to work. Here is the my Stack Blitz demo: https://stackblitz.com/edit/react-2gtf5s?file=src%2FApp.js

 

As you can see the scrollTrigger isn't working inside the Match Media. However, if you comment out that useLayoutEffect and use the other useLayoutEffect currently commented out, it works as intended. I figured I would start here to make sure I have the initial set-up inside the useLayoutEffect set-up correctly. 

 

Thank you so much!

Nicole 

Link to comment
Share on other sites

Hi there NIcole,

Proper animation cleanup is very important with frameworks, but especially with React. React 18 runs in strict mode locally by default which causes your useEffect() and useLayoutEffect() to get called TWICE.

In GSAP 3.11, we introduced a new gsap.context() feature that helps make animation cleanup a breeze. All you need to do is wrap your code in a context call. All GSAP animations and ScrollTriggers created within the function get collected up in that context so that you can easily revert() ALL of them at once.

Here's the structure:

// typically it's best to useLayoutEffect() instead of useEffect() to have React render the initial state properly from the very start.
useLayoutEffect(() => {
  let ctx = gsap.context(() => {
    // all your GSAP animation code here
  });
  return () => ctx.revert(); // <- cleanup!
}, []);


matchMedia is a lot like gsap.context (actually it uses context under the hood) So you need to do mm.revert() in your effect. Here's an example

 

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

We strongly recommend reading the React information we've put together at https://greensock.com/react

 

Happy tweening!

  • Like 1
Link to comment
Share on other sites

Hi Cassie!


Thank you so much for your quick response and explanation! 

 

My issue only happens within Match Media. I use the gsap.context() in other components that don't require Match Media and it works great! The addition of mm.revert() unfortunately didn't work in the Stack Blitz or my project. I would love to create a more comprehensive demo of the issue I'm experiencing in my project, but the match media won't function in the Stack Blitz either. As you can see here, when I use the context(), the scrollTrigger pins that image div and the image splits apart. But when using match media, the scrollTrigger doesn't work, even with the additional of mm.revert().

 

I've read the React documentation, but haven't seen any documentation specific to React and Match Media (unless I missed it!). Is there any other there I can review?

 

Thanks again!

Nicole

Link to comment
Share on other sites

Rodrigo, 

 

Thanks so much! That was a silly mistake on my part.

 

With Match Media working, I was able to create a demo of exactly what's happening in my project, which can be found here. You'll see that everything functions as it should until you get to Match Media's breakpoint. Whether going from mobile to desktop or desktop to mobile, if you cross that breakpoint, the horizontal slider moves up into the image slider. If you refresh the page, then it will function again normally, but the breakpoint seems to break any other Greensock functionality on the page. 

 

Any help here would be greatly appreciated!

 

Thanks so much!

Nicole 

Link to comment
Share on other sites

Heya!

 

Is this non-react version doing what you'd expect?

 

See the Pen poqJrwy?editors=0010 by GreenSock (@GreenSock) on CodePen



Also I'm not noticing a huge amount of difference between your two matchMedia's? What is it that you're changing? Just the amount that the images move?

x: -(window.innerWidth / 6),
   
 vs

x: -window.innerWidth,


If so you could write it out like this to avoid duplicating the entire timeline.

 

let mm = gsap.matchMedia(),
    breakPoint = 500;

mm.add({
  // set up any number of arbitrarily-named conditions. The function below will be called when ANY of them match.
  isDesktop: `(min-width: ${breakPoint}px)`,
  isMobile: `(max-width: ${breakPoint - 1}px)`,

}, (context) => {

  // context.conditions has a boolean property for each condition defined above indicating if it's matched or not.
  let { isDesktop, isMobile, reduceMotion } = context.conditions;

	...etc

    splitImageTimeline
    .to(".left", {
      x: isDesktop ? -(window.innerWidth / 6) : -window.innerWidth
      duration: 1
    })
  
    .... etc

}); 


Let me know if the vanilla demo's doing what you're after and we'll work from there.

Link to comment
Share on other sites

Hi Cassie!

 

Thanks for getting back to me! And thanks for sharing the alternate syntax! There are a few other conditionals in my real project I removed for the sake of the demo, but still think that option could be better! 

 

Unfortunately, I still experience the bug in the vanilla JS version as well. If you go from one size and cross the breakpoint defined in Match Media then the scroller moves up and across the image slider. Here is a screen recording of what I experience in the CodePen version which is the same as what I experience in the React version too. 

 

Thanks so much! Appreciate your help! 

Nicole 

Link to comment
Share on other sites

  • Solution

That's all good. At least we know it's not a React thing. Let's get it working in vanilla land first!

 

How about this?

 

See the Pen poqJrwy?editors=0010 by GreenSock (@GreenSock) on CodePen



I added a refreshPriority on the horizontal scroll as it's likely the refresh order is messed up as you have one inside a matchMedia and one outside

From the docs. -
 

ScrollTrigger.create({
  refreshPriority: -1, // lower priority makes it happen later in the refresh() calculations
  ...
});
ScrollTrigger.create({
  ... // if no refreshPriority is defined, it defaults to 0
});
gsap.to(".class", { // works with tweens/timelines too
  opacity: 1,
  scrollTrigger: {
    refreshPriority: 3, // a higher number makes it happen earlier in the refresh() calculations
    ...
  }
});

ScrollTrigger.sort(); // use the defaults (typically best)

// or use a custom function...
ScrollTrigger.sort((a, b) => a.start - b.start);

 

  • Like 2
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...