Skip to main content

3.8 release

· 6 min read
Cassie Evans
Jack Doyle
  • containerAnimation - vertical scrolling can animate a container horizontally; now you can set up ScrollTriggers on that horizontal movement. It's like having nested ScrollTriggers!
  • preventOverlaps & fastScrollEnd - when you jump to a section, do you have lingering animations that overlap? These features can save the day.
  • isInViewport() - a simple way to check if an element is in the viewport
  • positionInViewport() - find out exactly where an element is in relation to the viewport
  • Directional snapping - by default, snapping will now always go in the direction that the user last scrolled. Much more intuitive! There's even a .snapDirectional() utility method.



We try to keep an eye on common challenges that the GSAP community faces. A popular effect is to create horizontally-moving sections that are tied to vertical scrolling.

That's simple enough with the animated horizontal "scroll" technique where a pinned container is animated along the x-axis to emulate horizontal scrolling.

However, since the effect is an animation as opposed to native scroll, it's very tricky to trigger animations within this horizontally "scrolling" container.

Enter containerAnimation. 🥳

With containerAnimation, you can actually create ScrollTriggers on that fake-scrolling container to detect when an element is 'scrolled' into view horizontally and then trigger an animation. Think of it like a ScrollTrigger inside a ScrollTrigger. 🤯

  • First, we create a linear tween to handle the horizontal 'scroll'
  • Then we can pass that animation (as the containerAnimation) to the ScrollTriggers of tweens or timelines
// keep a reference of the horizontal 'fake scrolling' animation so we can pass it around
let scrollTween ='.container', {
xPercent: -100 * (sections.length - 1),
ease: 'none', // <-- IMPORTANT!
scrollTrigger: {
trigger: '.container',
start: 'top top',
end: '+=3000',
pin: true,
scrub: 0.5

// now let's create an animation that triggers based on the horizontal fake-scroll:'.box', {
y: -100,
scrollTrigger: {
trigger: '.box',
start: 'left center',
containerAnimation: scrollTween // <-- NEW!!

Check out this demo to see it in action!


preventOverlaps and fastScrollEnd

Creative Coding Club Walkthrough

Special thanks to Creative Coding Club for providing this video. We highly recommend their courses at Take your animation skills to the next level.

Scroll-triggered animations pose unique challenges. Unlike animations that play on load, you're putting control in the users' hands. They decide how fast to scroll, which direction to scroll and by association when the animations get triggered. With this in mind, we added two new features to ScrollTrigger to help avoid overlaps between successive non-scrubbed animations:

  • preventOverlaps kicks in as a ScrollTrigger is about to trigger an animation; it finds preceding scrollTrigger-based animations and forces those previous animations to their end state avoiding unsightly overlaps.
  • fastScrollEnd will force the current ScrollTrigger's animation to completion if you leave its trigger area above a certain velocity (default 2500px/s). This property can help to avoid overlapping animations when the user scrolls quickly.

We're especially excited about the UX benefits this brings. When users are in a hurry to access information, slow animation can be a frustrating hindrance to their goal. With fastScrollEnd, we can avoid wearing down the patience of task-focused users by quickly forcing animations to their end state when scrolling quickly.

scrollTrigger: {
trigger: ".container",
fastScrollEnd: true // (default 2500px/s)
// or
fastScrollEnd: 3000 // (custom 3000px/s velocity)

preventOverlaps: true // prevent overlaps in preceding scrollTrigger animations
// or
preventOverlaps: "group1" // prevent overlaps in specific group of scrollTrigger animations

You can take a look at how these new properties work, independently and in unison, by scrolling down in this demo at different speeds and toggling the options:


In addition to these new features we've added some handy methods to help you detect when an element is in view, and where.


ScrollTrigger.isInViewport() lets you find out if a particular element is in the viewport.

if (ScrollTrigger.isInViewport('#selector')) {
// in the viewport vertically

You can also find out if a certain proportion is in view. The following will return true if at least 20% of the element is in the viewport:

if (ScrollTrigger.isInViewport(element, 0.2)) {
// at least 20% of the element is in the viewport vertically

To check horizontally instead of vertically, just use the 3rd parameter (boolean):

if (ScrollTrigger.isInViewport(element, 0.2, true)) {
// at least 20% of the element is in the viewport horizontally


The ScrollTrigger.positionInViewPort() method lets you get a normalized value representing an element's position in relation to the viewport where 0 is at the top of the viewport, 0.5 is in the center, and 1 is at the bottom. So, for example, if the center of the element is 80% down from the top of the viewport, the following code would return 0.8:

ScrollTrigger.positionInViewport(element, 'center');

For the reference point (2nd parameter), you can use keywords like "center" (the default), "top", or "bottom". Or you can use a number of pixels from the element's top, so 20 would make the reference point 20 pixels down from the top of the element.



Directional snapping

By default, snapping will now always go in the direction that the user last scrolled. Much more intuitive! Previously, it would snap to the closest value regardless of direction which could lead to annoying snap-back behavior.

There's even a ScrollTrigger.snapDirectional() utility method that lets you do your own directional snapping for any numeric values. It returns a snapping function that you can feed a value to snap, and a direction where 1 is forward (greater than) and -1 is backward (less than). For example:

// returns a function that snaps to the closest increment of 5
let snap = ScrollTrigger.snapDirectional(5);
snap(11); // 10 (closest, not directional)
snap(11, 1); // 15 (closest greater than)
snap(11, -1); // 10 (closest less than)

You can even use an Array of values!

let values = [0, 5, 20, 100];
// returns a function that'll snap to the closest value in the Array
let snap = ScrollTrigger.snapDirectional(values);
snap(8); // 5 (closest, non-directional)
snap(8, 1); // 20 (closest greater than)
snap(99, -1); // 20 (closest less than)

And more...


Make sure to check out the ScrollTrigger docs for more information.

GSAP 3.8 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!