Animation of elements when scrolling a page using GSAP

in Front-end | January 14, 2020
Animation of elements when scrolling a page using GSAP

Let’s consider creating an animation of elements when scrolling a page using a specific task as an example.

What do we have

The section where two blocks are placed (fig. 1). On the left there is a title and a block with icons (1), on the right there are blocks with text (2). Hereafter, the blocks will be marked by the corresponding numbers. We need to make the following animation:

  1. When scrolling the page, when we reach the section we need, the block on the left is fixed and remains in its place until we scroll through this section.
  2. When the text block we need reaches a certain point on the screen (in our case, this point is equal to the indentation of 350px from the top of the screen). There is an animation (increase in size) of the icon on the left, which corresponds to this text block.
  3. Animation should work when scrolling both down the page and backwards.
Fig. 1

What do we use

To implement this animation, we will use the GSAP – JavaScript library to create hi-end animations.

Let’s get started

1. HTML

There is nothing special here. The section is divided into two blocks, with the set width. In the block on the left we put “block 1”, and on the right we have “block 2” (fig. 2).

Fig. 2

2. CSS

The block on the left has position: relative ;, and the nested “block 1”

position: absolute;
top: 0;
height: calc(100vh - 10rem);

We calculate the height from the calculation of 100% of the screen height minus top margin of our section, which is 10rem.

We also add the necessary styles for the classes that will set the positioning for our “block 1”:

.fixed_mod {
  position: fixed;
  top: 10rem;
}

.bottom_mod {
  position: absolute;
  top: auto;
  bottom: -10rem;
}

Animation of the icon will be the usual increase through scale, we will just add the desired class.

.scale_mod {
  transform: scale(1.2);
}

3. Javascript

We have the following libraries connected:

  1. jQuery
  2. TweenMax
  3. Our small script that helps us when working with animation of a page bound to a scroll. I will not fully describe it work, since we will speak about only a small part of it. We will write about this in a separate article. In short, it collects a data array, for example, such as: the height of the block we need, its coordinates relative to the beginning of the page, etc. An important feature is that TweenMax.ticker is used to make it easier for us to work with requestAnimationFrame.

So! Let’s get to the point :). Using the script we:

  • found the section we need
  • found out its height .outerHeight ();
  • coordinates relative to the beginning of the page .offset ().top;

Now we need to find out the scroll position relative to the beginning of the page. In the global visibility zone, we create the variable var $ scrollTop = 0; and hang it on the event handlers window.onload and window.onscroll its overtyping $ scrollTop = $(window).scrollTop ();

Also in the global visibility zone, add two empty arrays for our icons and text blocks.

var doohIconsArray = [],
    doohTextArray = [];

Next we do – create a function that will collect data for us, write them to arrays. We will call it on window.onload.

if ($doohIconsParent.length) {
  // Search for our icons and write them to an array
  $('.dooh_icon_wrap').each(function () {
    var icon = $(this);
    var iconObj = {
      'icon': icon
    };
    doohIconsArray.push(iconObj);
  });

  // Search for text blocks, find out their coordinates relative to the beginning of the page and write the parameters to an array
  $('.dooh_text_item', $doohTextList).each(function (i, el) {
    var el = $(this);
    var itemPos = el.offset().top;
    var textObj = {
      'item': el,
      'pos': itemPos
    };
    doohTextArray.push(textObj);
  });
}

One more moment! According to the terms of reference, our text blocks on screens less than 1024px wide become a slider and the icons are animated in accordance with the current slide. Therefore, we must not forget after resizing the window.onresize page to update the text array and all our data from the script so that everything works as it should.

if (doohTextArray) {
  $.each(doohTextArray, function (key, value) {
    value.pos = value.item.offset().top;
    value.height = value.item.outerHeight();
  });
}

if ($allAnimateElements) {
  $.each($allAnimateElements, function (key, value) {
    value.position = value.el.offset().top;
    value.height = value.el.hasClass("parallaxElement") ? windowHeight : value.el.outerHeight();
  });
}

Seems to have forgotten nothing:D. Go on…

Animation

We create our two functions. Let’s call them in our script with the necessary
arguments.

Fig. 3

We add flags to the global visibility zone that will help us to start the animation only in the place we need, and our indent from the top of the screen for the text block is 350px.

var textPos1 = false,
    textPos2 = false,
    textPos3 = false,
    textPos4 = false,
    textOffsetTop = $scrollTop + 350;

Icon animation

We create a function, pass the arguments we need and start adding there our conditions in order.

function doohIconsAnim(position, height, el) {}

Our main condition is the width of the screen. Earlier I mentioned the mobile version. We also check the section we need by class.

if (windowWidth > 1023 && el.hasClass('fixMenuPoint') {}

Analysis of the code using the animation of the 1st icon as an example.

Let’s write a condition that will check that the indent from the top of the screen for the text block textOffsetTop is more than or equal to the position of the first block relative to the beginning of the page doohTextArray [0] .pos and the indent from the top of the screen for the text block textOffsetTop is less than the position of the second doohTextArray [1] .pos block relative to the top of the page.

We will also check that the animation of the first icon is not running yet !textPos1. If the conditions are met, we go to the animation of the 1st icon, adding to it the necessary scale_mod class using GSAP TweenMax.set (doohIconsArray [0].icon, {className:
‘+ = scale_mod’});.

We also note that the animation of the 1st icon worked out textPos1 = true;. In an alternative branch, we write down the condition for the icon to return to its original state if our animation worked out, but we started to scroll up the page.

In this case, we check that textOffsetTop is less than the position of the first block relative to the beginning of the page doohTextArray [0].pos and the animation of the first icon was already performed by textPos1.

If the conditions are met, we remove the scale_mod from the icon and set the flag to its original position textPos1 = false;.

All animation code of the 1st icons:

if (textOffsetTop >= doohTextArray[0].pos &amp;&amp; textOffsetTop < doohTextArray[1].pos &amp;&amp; !textPos1) {
  textPos1 = true;
  TweenMax.set(doohIconsArray[0].icon, { className: '+=scale_mod' });
} else {
  if (textOffsetTop < doohTextArray[0].pos &amp;&amp; textPos1) {
    textPos1 = false;
    TweenMax.set(doohIconsArray[0].icon, { className: '-=scale_mod' });
  }
}

All later conditions are similar, the only difference is in scope. Below is the entire animation code:

function doohIconsAnim(position, height, el) {
  if (windowWidth > 1023 &amp;&amp; el.hasClass('fixMenuPoint')) {
    if (textOffsetTop >= doohTextArray[0].pos &amp;&amp; textOffsetTop < doohTextArray[1].pos &amp;&amp; !textPos1) {
      textPos1 = true;
      TweenMax.set(doohIconsArray[0].icon, { className: '+=scale_mod' });
    } else {
      if (textOffsetTop < doohTextArray[0].pos &amp;&amp; textPos1) {
        textPos1 = false;
        TweenMax.set(doohIconsArray[0].icon, { className: '-=scale_mod' });
      }
    }

    if ($scrollTop >= position) {
      if (textOffsetTop >= doohTextArray[1].pos &amp;&amp; textOffsetTop < doohTextArray[2].pos &amp;&amp; !textPos2) {
        textPos2 = true;
        textPos1 = false;
        TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
        TweenMax.set(doohIconsArray[1].icon, { className: '+=scale_mod' });
      } else if (textOffsetTop >= doohTextArray[2].pos &amp;&amp; textOffsetTop < doohTextArray[3].pos &amp;&amp; !textPos3) {
        textPos3 = true;
        TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
        TweenMax.set(doohIconsArray[2].icon, { className: '+=scale_mod' });
      } else if (textOffsetTop >= doohTextArray[3].pos &amp;&amp; !textPos4) {
        textPos4 = true;
        TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
        TweenMax.set(doohIconsArray[3].icon, { className: '+=scale_mod' });
      } else {
        if (textOffsetTop < doohTextArray[1].pos &amp;&amp; textOffsetTop >= doohTextArray[0].pos &amp;&amp; textPos2) {
          textPos2 = false;
          TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
          TweenMax.set(doohIconsArray[0].icon, { className: '+=scale_mod' });
        } else if (textOffsetTop < doohTextArray[2].pos &amp;&amp; textOffsetTop >= doohTextArray[1].pos &amp;&amp; textPos3) {
          textPos3 = false;
          TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
          TweenMax.set(doohIconsArray[1].icon, { className: '+=scale_mod' });
        } else if (textOffsetTop < doohTextArray[3].pos &amp;&amp; textOffsetTop >= doohTextArray[2].pos &amp;&amp; textPos4) {
          textPos4 = false;
          TweenMax.set('.dooh_icon_wrap.scale_mod', { className: '-=scale_mod' });
          TweenMax.set(doohIconsArray[2].icon, { className: '+=scale_mod' });
        }
      }
    }
  }
}

Block fixation on the left

According to our terms of reference, “block 1” should be fixed while we go through our section.

We’ll add flags to the global visibility zone, they, as well as working with icons, will help us to clearly control so that the animation runs strictly at the right time for it.

var doohFixed = false,
    doohBottom = false;

We pass the following arguments to our function:

  • el – our section
  • position – the position of the section relative to the beginning of the page.
  • height – section height

At the beginning we check that the screen width is more than 1023px and our section has the corresponding class. Further, we have three conditions – three positions at which our “block 1” gets or loses the class we need. We will also add classes using GSAP.

Condition 1: If the scroll position is bigger than the section position relative to the top of the page.

$scrollTop > position

It means we are at the beginning of our section. We will also check that there has not yet been a block fixation.

!doohFixed

If the conditions are met, we add to our block the necessary position: fixed; to fix it

TweenMax.set($doohIconsParent, {className: '+=fixed_mod'});

and note that the block fixation was done doohFixed = true;. In the same place we add the condition when we were at the bottom of the section and come back, fixing the

$scrollTop + windowHeight < position + height

block again. In this case we must take away position: absolute; from our

TweenMax.set($doohIconsParent, {className: '-=bottom_mod'});

and set the necessary value to our flag doohBottom = false;

Condition 2: When we reach the end of the section or are under it, “block 1” is no longer fixed. Let’s check it like this: the scroll position + screen height is more than or equal to the section position + its height $scrollTop + windowHeight> = position + height. Remember to check that we haven’t done this animation yet !doohBottom. If the conditions are met, we remove position: fixed; and add position: absolute ;, note that this animation worked out doohBottom = true; and note that our block is no longer fixed doohFixed = false;.

Condition 3: This condition should work out when our block is fixed, and we scroll the page up, i.e, we return to its original position. We indicate the necessary condition: the scroll position is less than or equal to the position of the section and our block of icons was currently fixed $scrollTop <= position && doohFixed. If the rule is observed, we remove the fixation of our TweenMax.set block ($doohIconsParent, {className: ‘- = fixed_mod’}); and note that our block is no longer fixed doohFixed = false;.

All code of block fixation:

function doohPositionAnim(position, height, el) {
  if (windowWidth > 1023 &amp;&amp; el.hasClass('fixMenuPoint')) {
    if ($scrollTop > position &amp;&amp; $scrollTop + windowHeight < position + height &amp;&amp; !doohFixed) {
      TweenMax.set($doohIconsParent, { className: '+=fixed_mod' });
      TweenMax.set($doohIconsParent, { className: '-=bottom_mod' });
      doohFixed = true;
      doohBottom = false;
    } else if ($scrollTop + windowHeight >= position + height &amp;&amp; !doohBottom) {
      doohBottom = true;
      doohFixed = false;
      TweenMax.set($doohIconsParent, { className: '-=fixed_mod' });
      TweenMax.set($doohIconsParent, { className: '+=bottom_mod' });
    } else if ($scrollTop <= position &amp;&amp; doohFixed) {
      doohFixed = false;
      TweenMax.set($doohIconsParent, { className: '-=fixed_mod' });
    }
  }
}

That’s all.