Parallax Scroll Animations with the Intersection Observer API and GSAP3

How to ditch scroll-interaction libraries for GreenSock powered parallax animations.

Ryan LaBar
Elegant Seagulls
5 min readMar 11, 2020

--

Metaphorical roller coaster representing parallax web animation implementation
Avoid the roller coaster ride of scroll-interaction libraries, and keep the thrills of crafting performant parallax animations with Intersection Observer API and GSAP3. Photo by Matt Bowden on Unsplash.

Scroll-interaction (parallax animation) libraries are often, well, less-than magical from both development and performance standpoints. These libraries are generally packed with more features than you’ll use, aren’t easy to tree shake, and usually use the scroll event listener to do the calculations for the positioning and animation progress, which, if you aren’t careful, can negatively effect performance and cause a jittering effect on your site.

Here’s how we ditched the need for scroll-interaction libraries:

The Technologies:

GreenSock Animation Platform (GSAP) is (arguably) the most mature and performant JavaScript animation library available. GSAP recently launched it’s version 3, which added a ton of new and powerful features, in a package size about half that of 2.x. For this tutorial, a base-line knowledge of GSAP is assumed.

The Intersection Observer API is used to determine whether or not an element is in the viewport. It’s performant (no full-time on scroll event listeners). The only downside is, ≤ IE11 doesn’t support it. For those who still need to support IE11, there’s a polyfill. There are several other things that won’t work in IE11 in this tutorial, mostly related to the es6 js syntax. If you’re supporting IE11, use a syntax transformer.

Intersection Observer Setup

The Intersection Observer Constructor takes two parameters: the callback function, and options, which allow users to fine tune when, and at what level of precision the Intersection Observer fires. Note: in CodePen the rootMargin option won’t work unless you’re in debug mode.

See it in action:

Fairly straight forward.

Adding the Loop

If you have a repeating scroll-triggered animation pattern across a page, setup is as simple as looping through the targets and sending those to the Intersection Observer constructor’s callback.

See it in action:

To see the effect of the -25% rootMargin, CodePen needs to be opened in debug mode.

Parallax / Scroll-Tied Animations

Parallaxing complicates things just a bit. This is because we’ll need to start watching the position of an element once it’s intersecting, and also will need to control a specific tween over a specified scroll distance.

To watch the element’s position once it’s intersecting, and calculate the progress of the tween, I use GSAP’s Ticker. The Ticker is automatically throttled to the browser window’s requestAnimationFrame (rAF), which is the rate/timing that the browser paints to the screen. Like rAF, GSAP’s ticker is controlled by a callback function.

To help keep everything properly referenced, and because removing an added Ticker requires a reference to a specific callback function, we’ll move everything inside our initial forEach Loop. This also helps ensure that the ticker isn’t stopped if an element moves off screen while another is still intersecting.

The new loop will look similar to this:

Note: if having the ticker running the entire time the target is in view bothers you, you could very easily convert this to use an on scroll listener throttled to rAF, adding and removing the event when the element is in view, but I haven’t seen any negative impacts of just having the ticker running.

The Math (don’t worry, it’s not bad)

To tween the timeline’s progress value from 0 to 1, we’ll need to create a fraction that divides the element’s current position relative to (usually) the bottom of the viewport by the scroll distance over which we’d like the animation to take place.

To get the top value of the fraction, we’ll get the window’s height, add that to the window’s current scroll position, and subtract the trigger element’s top offset value. The bottom of the fraction can be any pixel duration, but in general will be some combination of the element and viewport’s height. For an animation that lasts the entire viewport (many parallax effects) it would be the viewport’s height plus the height of the element. So you end up with a progress equation looking like:

Putting it All Together

To add animations, we’ll define our gsap.timeline() relative to our element in the forEach loop, so that it can be referenced in the gsap.ticker callback function.

All together the basic code setup will look like:

Live example:

Expanding to React JS

We usually use a similar setup in our React JS builds, but use the React Intersection Observer and a custom hook to start and stop the GSAP Ticker.

If there’s interest, I’ll write another article detailing our React JS setup for this.

Other Methods

This article avoids using the scroll event listener, but there are times when it’s handy, or even necessary; for example: Triggering animations on position fixed/sticky elements, or if you need to support IE11, etc. I’m not going to go into step-by-step details on implementation, in this article, but the code should be pretty easy to dissect from this example:

--

--