Jump to content
GreenSock

GSAP 3.11 Released


| GreenSock
8518
  • gsap.matchMedia() is a game-changer for responsive, accessible-friendly animations. 💚
  • gsap.context() that greatly simplifies setup and reverting of a bunch of animations/ScrollTriggers, especially for React developers!
  • You can now revert() any animation to return the targets to their original state.
  • Set lockAxis: true on an Observer to make it lock into whichever direction the user first drags

One of the hardest challenges for web-animators is crafting animations that work seamlessly on all screen sizes and respect users motion preferences. Well, not anymore!

gsap.matchMedia() lets you easily tuck setup code into a function that only executes when a particular media query matches and then when it no longer matches, all the GSAP animations and ScrollTriggers created during that function's execution get reverted automatically! Customizing for mobile/desktop or prefers-reduced-motion is remarkably simple and incredibly flexible.

// create
let mm = gsap.matchMedia();

// add a media query. When it matches, the associated function will run
mm.add("(min-width: 800px)", () => {

  // this setup code only runs when viewport is at least 800px wide
  gsap.to(...);
  gsap.from(...);
  ScrollTrigger.create({...}); 

  return () => { // optional
    // custom cleanup code here (runs when it STOPS matching)
  };
});

// later, if we need to revert all the animations/ScrollTriggers...
mm.revert();

What if your setup code for various media queries is mostly identical but a few key values are different? If you add() each media query individually, you may end up with a lot of redundant code. Just use the conditions syntax! Instead of a string for the first parameter, use an object with arbitrarily-named conditions and then the function will get called when any of those conditions match and you can check each condition as a boolean (matching or not). The conditions object could look like this:

{
  isDesktop: "(min-width: 800px)",
  isMobile: "(max-width: 799px)",
  reduceMotion: "(prefers-reduced-motion: reduce)"
}

Name your conditions whatever you want.

Below we'll set the breakpoint at 800px wide and honor the user's prefers-reduced-motion preference, leveraging the same setup code and using conditional logic where necessary:

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

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)`,
  reduceMotion: "(prefers-reduced-motion: reduce)"

}, (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;

  gsap.to(".box", {
    rotation: isDesktop ? 360 : 180, // spin further if desktop
    duration: reduceMotion ? 0 : 2 // skip to the end if prefers-reduced-motion
  });

  return () => { 
    // optionally return a cleanup function that will be called when none of the conditions match anymore (after having matched)
  }
}); 

Nice and concise! 🎉

You can set a scope so that all selector text inside the function maps only to descendants of a particular element or React Ref or Angular ElementRef. This can greatly simplify your code. See the full documentation for all the details.

A gsap.context() offers two key benefits:

  • 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.
  • [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!

This is effectively a silver bullet for the React 18 "double-call of useEffect() in Strict Mode" issue that normally breaks from() logic - just call revert() on the Context in your useEffect() cleanup function:

See the full documentation for all the details.

What if you want to revert an element back to its state BEFORE it was animated? You could just animation.progress(0), right? Sort of. Consider this element:

<div class="box"></div>

No inline styles at all. The opacity is 1 (the default) and then you do this: 

// fade out
let tween = gsap.to(".box", {opacity: 0});

Now let's try getting back to the original state:

tween.progress(0).pause();

That does indeed set it back to the starting values that GSAP parsed from the computed style:

<!-- inline style is still present -->
<div class="box" style="opacity: 1"></div> 

But that means it still has inline styles. Usually that doesn't matter, but perhaps a media query CSS rule sets opacity to 0.5 on that element. Doh! The inline style will overrule the class rule. So we need a way to have a tween/timeline keep track of the original inline styles and REMOVE the ones that it added. That requires a new method because progress(0) SHOULD set inline styles to ensure the state is what it's supposed to be at that point in the animation.

Solution: animation.revert()

GSAP 3.11 adds a new .revert() method to all Tweens and Timelines, so it's as simple as: 

animation.revert(); // removes inline styles that were added by the animation

Set lockAxis: true on an Observer and it'll watch the direction of the very first drag move (with type: "pointer" and/or "touch") and lock into that direction until the user releases the pointer/touch. There's even an onLockAxis() callback that you can tie into.

And more...

GSAP 3.11 also delivers various bug fixes, so we'd highly recommend installing the latest version today. There are many ways to get GSAP - see the Installation page for all the options (download, NPM, zip, Github, etc.).

Happy tweening!

  • Like 5

Get an all-access pass to premium plugins, offers, and more!

Join the Club

Cook up some delightful animation today with a generous dose of GSAP.

- Team GreenSock



User Feedback

Recommended Comments

Great work as always!
I'm impressed!
You are giving us great tools to do the right things - but it also goes to show, how complex doing 'perfect' web-design/development... has become - especially once you realize that mobile and desktop (while gerat for tutorial explanations) aren't by far the only interesting environments and reduced-motion also has partners in crime with high contrast, dark mode etc...

So thank you for giving us the option, and no thanks for taking away the excuses... ;-)

  • Haha 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

×