Introduction to GSAP in Bricks

In this tutorial, we’ll learn how to run basic GSAP animations inside the Bricks Builder using JSON objects inside specific data-attributes.

Table of Contents

Introduction

Not everyone like animations. But if – like me – you do, GSAP is the #1 must-know library in the JavaScript ecosystem.

GSAP is a complex library used by advanced front-end developers. To use it at its full potential, you’ll need a solid JavaScript background. But what if we could simplify the process and manage the library directly inside the Bricksbuilder?

This is the scope of the tutorial: get you started with the basics of GSAP and provide a set of attributes to easily manage your tweens and timelines inside the builder.

The advantages of this approach are:

  • the JavaScript Helper is ridiculously lightweight and shouldn’t negatively impact your website performance.
  • This method is compatible with all the GSAP parameters and most (if not all) the GSAP plugins, including the popular ScrollTrigger plugin.
  • You won’t need to deal with separated init files or inline codes for each page – all the options are managed inside the builder.
  • Once you get confident with the different attributes, setting tweens and timelines is a matter of minutes.
  • You might learn a couple of JavaScript/JSON logic.

Enqueue the Library

Download the library on this page: https://greensock.com/docs/v3/Installation. Upload and enqueue the minified gsap.min.js file inside your Child theme.

If you plan to use the ScrollTrigger plugin, make sure to upload and enqueue the ScrollTrigger.min.js file as well.

My preferred method to enqueue my scripts is described in this article.

The Javascript

Make sure to run the following code after the GSAP library (and all the plugins) have been initialized:

// Register the GSAP Plugin
gsap.registerPlugin(ScrollTrigger);

// Run the script after the DOM content is loaded
window.addEventListener('DOMContentLoaded', () => {

   // Tweens
   const gsapTweens = document.querySelectorAll('[data-tween]');
   const tweenFn = (arr) => {
      // Loop into each tween
      arr.forEach(tween => {

         // Get the tween type
         const type = tween.dataset.tween;

         let selector = tween;
         if (tween.dataset.tweenSelector) selector = tween.dataset.tweenSelector;

         // Get the tween option parameters
         const options = JSON.parse(tween.dataset.tweenOptions);

         // Run the tween based on the type of the animation
         switch (type) {
            case "to":
               gsap.to(selector, options);
               break;
            case "from":
               gsap.from(selector, options);
               break;
            case "fromTo":
               gsap.fromTo(selector, options.from, options.to);
               break;
         };
      });
   }

   // Check if there are tweens on the page  
   if (gsapTweens.length > 0) tweenFn(gsapTweens);


   // Timeline
   const gsapContainers = document.querySelectorAll('[data-tl-container="true"]');

   const timelineFn = (arr) => {
      // Loop into each timeline container
      arr.forEach(container => {

         // Get the timeline name
         const containerName = container.dataset.tlName;

         // Get the timeline options if any
         let containerOptions;
         if (container.dataset.tlOptions) containerOptions = JSON.parse(container.dataset.tlOptions);

         // Create an Array where we'll store all the tween options
         let tweensOptions = [];

         // Query the nested tweens that are part the timeline
         const tweens = container.querySelectorAll('[data-tl-tween][data-tl-name=' + containerName + ']');
         tweens.forEach(tween => {

            // Get the Timeline name related to the tween
            const tweenTl = tween.dataset.tlName;

            // Get the tween type
            let tweenType = tween.dataset.tlTween;

            // Set the selector
            let tweenSelector = tween;
            if (tween.dataset.tlTweenSelector) tweenSelector = tween.dataset.tlTweenSelector;

            // Get the order of the tween inside the timeline. Default is 999.
            let tweenOrder = 999;
            if (tween.dataset.tlTweenOrder) tweenOrder = parseInt(tween.dataset.tlTweenOrder);

            // Get the position of the tween inside the timeline. Default is "-=0"
            let tweenPosition = "+=0";
            if (tween.dataset.tlTweenPosition) tweenPosition = tween.dataset.tlTweenPosition;

            // Get the tween options
            const tweenOptions = tween.dataset.tlTweenOptions;

            const splitOptions = tweenOptions.split(';');
            splitOptions.forEach(option => {
               option = JSON.parse(option);

               let options;
               if (option.options) options = option.options;

               // override selector
               let selector = tweenSelector;
               if (option.selector) selector = option.selector;

               // override order
               let order = tweenOrder;
               if (option.order) order = parseInt(option.order);

               // override position
               let position = tweenPosition;
               if (option.position) position = option.position;

               // override type
               let type = tweenType;
               if (option.type) type = option.type;

               // Push all our options inside an Array
               tweensOptions.push({
                  selector: selector,
                  timeline: tweenTl,
                  type: type,
                  options: options,
                  order: order,
                  position: position,
               })
            })
         })

         // Sort the array based on the order parameter
         tweensOptions = Object.values(tweensOptions).sort((a, b) => a.order - b.order);

         // Initialize the timeline
         const tl = gsap.timeline(containerOptions);

         // Loop inside our tween options Array
         tweensOptions.forEach(el => {

            // Add the tween to the timeline
            switch (el.type) {
               case "to":
                  tl.to(el.selector, el.options, el.position);
                  break;
               case "from":
                  tl.from(el.selector, el.options, el.position);
                  break;
               case "fromTo":
                  tl.fromTo(el.selector, el.options.from, el.options.to, el.position);
                  break;
            }
         })
      })
   }

   // Check if there are timelines set on the page
   if (gsapContainers.length > 0) timelineFn(gsapContainers);

})

Pay attention to the first row of this code: we decided to register the ScrollTrigger plugin. You may want to delete that line if you have no intention to use it or register another GSAP plugin using the same method.

Add the animations inside Bricks

Let’s dive into the more popular features of GSAP. In this section, we’ll learn:

  • how to set simple tweens (from/to/fromTo)
  • how to create a timeline and add nested tweens to it
  • how to bind these animations to scroll using the ScrollTrigger plugin

Tweens

In simple words, a tween is a single animation attached to a target (understand an element of our website). A single tween can contain different animations on multiple parameters such as color, shape, size, position, etc… For further information, refer to the official documentation.

from

Think of a gsap.from() like a backwards tween where you define where the values should START, and then it animates to the current state which is perfect for animating objects onto the screen because you can set them up the way you want them to look at the end and then animate in from elsewhere.

official documentation

Setting a from tween is really easy with our JavaScript helper. Just create a data-attribute called data-tween on the element you want to animate and enter from as the value:

The next step is to insert all the option’s parameters as a JSON object. To convert/validate your JSON object, I’d recommend using this free online JSON formatted.

Here is an example of JSON object with the parameters we want to animate:

{
   "right":"0",
   "rotation":"720",
   "borderRadius":"50%",
   "backgroundColor":"#2fff00",
   "duration":"2"
}

Let’s create a new data-attribute called data-tween-options and paste the JSON object into it:

And that’s it! Refresh your front page and see the animation running just after the page is loaded.

to

The most common type of animation is a to() tween because it allows you to define the destination values (and most people think in terms of animating to certain values).

official documentation

The to animation works exactly like the “from” animation described above. The only difference in our settings is to type to as our data-tween attribute:

Remember, while from sets the initial state of the animation, to sets the final state – so choose your parameters accordingly.

fromTo

gsap.fromTo() tween lets you define BOTH the starting and ending values for an animation (as opposed to from() and to() tweens which use the current state as either the start or end). This is great for having full control over an animation, especially when it is chained with other animations.

official documentation

To set a fromTo animation, we need to set the data-tween attribute to fromTo:

Then we need to include both parameters of the from and the to inside our JSON. The structure of our JSON will look like this:

{
   "from":{
      ...
   },
   "to":{
      ...
   }
}

Here is one example:

{
   "from":{
      "borderRadius":"0%",
      "backgroundColor":"#000000"
   },
   "to":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   }
}

let’s paste it inside our data-tween-options attribute:

And that’s it!

Global Selectors (optional)

By default, the animation is applied to the element where the attributes are set. There are cases where you want to apply the animation on a custom selector – let’s say a class – and target all the elements that match the custom selector.

To achieve that, we introduced an optional attribute called data-tween-selector that accepts any custom selector as a value:

With this optional attribute, you have the ability to build your global animations on the page, and avoid adding the same attributes for each element. It also opens to more advanced features such as staggers.

Timelines

Timeline is a powerful sequencing tool that acts as a container for tweens and other timelines, making it simple to control them as a whole and precisely manage their timing. Without Timelines, building complex sequences would be far more cumbersome because you’d need to use a delay for every animation.

official documentation

The idea here is to create tweens that are related one to the other. A typical use of timelines in web development is when we want to animate different elements in a predefined sequence: for example, we want to fade in the header of a section, then the description text, then zoom in the call-to-action button, and finally reveal the corresponding image.

In order to create a timeline with our Javascript Helper, we’ll need to set a timeline container and link all the nested tweens to it.

Let’s see how to do it

Container

The timeline container is the parent element of all our nested tweens. We can set it on any Bricks element. The DOM structure will look like this:

Tweens don’t need to be direct children but need to be children.

To set a timeline, we just need to set a couple of data attributes.

data-tl-container

The first attribute is data-tl-container with a value of true:

data-tl-name

The second mandatory attribute is the data-tl-name. Just choose the one you like, such as tl1, timeline1 o headerTimeline.

Remember the timeline’s name because we’ll need it later when we’ll associate the related tweens to this specific timeline.

If you plan to set multiple timelines on your page, make sure to set a unique name for each timeline.

data-tl-options (optional)

You have the possibility to set global parameters for the whole timeline by creating a data-tl-options attribute and inserting the options as a JSON object.

This attribute is optional. If no options are added, the timeline will use the default GSAP values.

Now our timeline is set, let’s add some tweens to it!

Nested tweens

Creating nested tweens inside a timeline is really similar to the single tween configuration described above. We just need to associate it with our timeline by inserting the corresponding timeline’s name.

data-tl-name

Let’s type the timeline’s name we set earlier on our timeline container:

Now our tween is part of the timeline. You’ll add the same attribute to all the tweens that are part of the same timeline

data-tl-tween-options

This is exactly the same attribute as data-tween-options described earlier but the JSON structure is slightly modified. In a timeline, we need to wrap our options inside an options property :

The JSON structure will look like:

{
   "options":{
      "...your parameters..."
   }
}

Example:

{
   "options":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   }
}
data-tl-tween

This is exactly the same attribute as data-tween for single tweens: this field accepts to, from, and fromTo as values.

When dealing with several tweens on the same element, you are able to override this value in the JSON object by specifying the type property:

{
   "options":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   },
   "type":"from"
}
data-tl-tween-selector (optional)

Just as the data-tween-selector attribute for tweens described earlier, you can apply your animations on custom selectors inside a timeline using the data-tl-tween-selector attribute:

You can also override this value in the JSON object by specifying the selector property:

{
   "options":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   },
   "selector":".box2"
}
data-tl-tween-order (optional)

You may want to explicitly set the order of each tween inside the timeline. for example, you may want to animate the last DOM element of your container first, then animate the other element. In this case, you can just set the custom order inside the data-tl-tween-order attribute:

This attribute is optional. If it’s left blank, the default order value is 999.

You can also override this value in the JSON object by specifying the order property:

{
   "options":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   },
   "order":"1"
}
data-tl-tween-position (optional)

The secret to building gorgeous sequences with precise timing is understanding the position parameter which is used in many methods throughout GSAP. This one super-flexible parameter controls the placement of your tweens, labels, callbacks, pauses, and even nested timelines, so you’ll be able to literally place anything anywhere in any sequence.

official documentation

The default behavior of a timeline is to run tween B after tween A has finished the animation. There are cases where you want to start tween B before (or after) tween A is completed. This is where the data-tl-tween-position attribute is useful.

Let’s say we want to run our tween one second after the previous tween has completed his animation. We’ll set the attribute like this:

This attribute is optional. The default value is +=0. Check the official documentation to see all the different value types you can use.

You can also override this value in the JSON object by specifying the position property:

{
   "options":{
      "right":"0",
      "rotation":"720",
      "borderRadius":"50%",
      "backgroundColor":"#2fff00",
      "duration":"2"
   },
   "position":"+=1"
}

Multiple tweens on the same element

Sometimes you need to add several animations on the same element at different positions of the timeline. No problem with our Javascript Helper: just separate your different JSON objects by “;” inside the data-tl-tween-options attribute.

Here is an example:

{
   "options":{
      "autoAlpha":"1"
   },
   "order":"0"
};{
   "options":{
      "autoAlpha":"0"
   },
   "order":"2"
};{
   "options":{
      "autoAlpha":"1"
   },
   "order":"4"
}

Note that this is not a valid JSON since “;” is an invalid separator. But no worries, the script will first split the 3 objects and parse the JSON afterward.

ScrollTrigger

ScrollTrigger creates jaw-dropping scroll-based animations with minimal code. Or trigger anything scroll-related, even if it has nothing to do with animation.

offilial documentation

If you followed all the steps above, you’ve correctly set your tweens and/or your timeline and it’s running just fine as soon as the page is loaded.

But what if you want to run your animations once the visitor scrolls to a specific section of your website instead of instantly after the page is loaded?

That’s super easy to do easing the ScrollTrigger Plugin of GSAP. Let’s see how.

Bind the animation when an element is visible inside the viewport

To activate ScrollTrigger inside your tween or your timeline, you’ll just have to add a couple of lines in your JSON option file. Here is the structure:

{
   "scrollTrigger":{
      "trigger":"#selector",
      "scrub":false,
   },
   ...your normal parameter...
}

Let’s see it in action with our previous example:

{
   "scrollTrigger":{
      "trigger":"#container1",
      "scrub":false,
   },
   "right":"0",
   "rotation":"720",
   "borderRadius":"50%",
   "backgroundColor":"#2fff00",
   "duration":"2"
}

Pay attention to this line: “trigger”:”#container1″. #container1 is the ID of the element that will trigger our animation once it will be visible inside the viewport.

We used “scrub”:false here in order to run the entire animation once the trigger is activated. We’ll see later what happens when scrub is set to true.

Understanding the trigger position of the viewport

Incredible flexibility for defining scroll positions – like “start when the center of this element hits the center of the viewport, and end when the bottom of that other element hits the bottom of the viewport”, use keywords (top, center, bottom, left, right), percentages, pixels, or even relative values like "+=300px". Once you get the hang of the syntax, it’s remarkably intuitive.

official documentation

You can easily define when your trigger should be activated based on where the element is on the viewport.

Let’s say we want to activate our animation when the top of our trigger’s element touches the center of the viewport, we’ll just add this one-line code:

{
   "scrollTrigger":{
      "trigger":"#selector",
      "scrub":false,
      "start":"top center"
   },
   ...your normal parameter...
}

if you want to activate visual markers on the website (not recommended on production sites) and see exactly how the triggers start and end, just add “markers”:true to your JSON object like this:

{
   "scrollTrigger":{
      "trigger":"#selector",
      "scrub":false,
      "start":"top center",
      "markers":true,
   },
   ...your normal parameter...
}

Bind the animation to the movement of the mouse

ScrollTriggers can perform an actions on an animation (play, pause, resume, restart, reverse, complete, reset) when entering/leaving the defined area or link it directly to the scrollbar so that it acts like a scrubber (scrub: true).

official documentation

Now, this is where the magic happens. You may not want to fire the entire animation when the trigger is visible inside the viewport, but you’d rather want to bind the animation to the scroll of the visitor’s mouse. Well, it’s super easy: set the scrub to true:

{
   "scrollTrigger":{
      "trigger":"#selector",
      "scrub":true,
      "start":"top center"
   },
   ...your normal parameter...
}

You can also Soften the link between the animation and the scrollbar so that it takes a certain amount of time to “catch up”, like scrub: 1 would take one second to catch up.

{
   "scrollTrigger":{
      "trigger":"#selector",
      "scrub":1,
      "start":"top center"
   },
   ...your normal parameter...
}

That will create a smooth animation when scrolling.

Final Tip

I can’t finish this tutorial without mentioning the super useful cheat sheet produced by Greensock for all the GSAP-related animations: https://greensock.com/cheatsheet/. Believe me, this is gold!

Instant access to 390+ Bricks code tutorials with BricksLabs Pro

4 comments

  • Thanks! With respect to the ScrollTrigger... I can't figure out how to use it with fromTo. There's a line saying
    "...your normal parameter..."
    ...I've tried every possible option but the animation plays right away and the ScrollTrigger part is being ignored.

  • Michael van Dinther

    Where should Global Selectors be set?
    Ideally from a central location within Bricks I suppose.

    • A
      David Browne

      Hi Michael,

      It wasn't me that wrote the tutorial, but the way that is being shown here, you'd add the 'data-tween-selector' as an attribute to an element, in the same way as the other attributes.

      It just means that you'll only need to add it once to a page, and any other elements with the same class (the class that matches the selector you choose) will pick up the same animation. Instead of having to add attributes to every element individually.

      It's not something that can be applied globally across an entire site, using this method (because it's dependent on elements having custom attributes), unless you were adding it to an element in a header/footer template, which then would be global. Each page would need at least one element with the attributes added.

      I would personally target using JS, rather than using attributes, if trying to create animations for an entire site, but that's a different method to what is being used here.

    • Michael van Dinther

      Hey team, possible to answer this question please?

Leave your comment