Buildin' an accordion animation using GSAP

You have no idea what an "accordion" is, do you?

Good morning beautiful bitches! Today we are gonna learn how to create an accordion animation like this in today's post

So without any unnecessary banter let's get started!

Setting up the HTML

Nothing major just a basic HTML setup where the menu part of the group will be the visible one

<div class="accordion">

  <div class="accordion-group">
    <div class="accordion-menu">
      Alpha
    </div>
    <div class="accordion-content">
      <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
    </div>
  </div>

  <div class="accordion-group">
    <div class="accordion-menu">
      Bravo
    </div>
    <div class="accordion-content">
      <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
    </div>
  </div>

  <div class="accordion-group">
    <div class="accordion-menu">
      Charlie
    </div>
    <div class="accordion-content">
      <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
    </div>
  </div>
  
  <div class="accordion-group">
    <div class="accordion-menu">
      Delta
    </div>
    <div class="accordion-content">
      <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
    </div>
  </div>
  
  <div class="accordion-group">
    <div class="accordion-menu">
      Echo
    </div>
    <div class="accordion-content">
      <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
    </div>
  </div>

</div>

Adding a bit of CSS

Again to keep it minimal and reusable I am keeping the CSS to a minimum

* {
  box-sizing: border-box;
}

.accordion {
  width: 250px;
  margin: 15px;
  box-shadow: 0 8px 17px rgba(0,0,0,.2);
}

.accordion-menu {
  font-size: 15px;
  background: #0277BD;
  color: white;
  padding: 10px;
  cursor: pointer;
  user-select: none;
  box-shadow: 0 2px 5px 0 rgba(0,0,0,.26);
}

.accordion-content {
  height: 0;
  overflow: hidden;
  font-size: 14px;
}

.accordion-content.expanded {
  height: 0;
  overflow: hidden;
}

p {
  margin: 10px;
}

This is gonna give you something that looks like this:

The Javascript

This is where all the magic happens. Now for all the animations and stuff, I use something called as GSAP basically all the animations that you see out there on my website or on any other is basically using this. Very very powerful thing using which you literally create any kind of imaginable animation.

you can refer to the installation docs here in case you might wanna use it

// toArray GSAP tool
// <https://greensock.com/docs/v3/GSAP/UtilityMethods/toArray()>

// Store an array with .accordion-group elements
let groups = gsap.utils.toArray(".accordion-group");

// Store an array with .accordion-menu elements
let menus = gsap.utils.toArray(".accordion-menu");

// Apply createAnimation(element) for each array element
// This creates animations for each .accordion-group
// and store it in a variable
let animations = groups.map(createAnimation);

// Add click event listener to each .accordion-menu
// that fires playAnimation(selected) on click
menus.forEach(menu => {
  menu.addEventListener("click", () => playAnimation(menu));
});

// using map to create an array of functions and then looping through those functions passing in targets
function playAnimation(selected) {
  animations.forEach(animate => animate(selected))
}

// CreateAnimation function
function createAnimation(element) {
  
  // Create colections of .accordion-menu and .accordion-content
  let menu = element.querySelector(".accordion-menu");
  let box  = element.querySelector(".accordion-content");
  
  // GSAP initial set height of .accordion-content to auto
  gsap.set(box, { height: "auto"})

  // GSAP tween reversed 
  let tween = gsap.from(box, { 
    height: 0, 
    duration: 0.5, 
    ease: "power1.inOut" 
  }).reverse();

  // CreateAnimation() returns the tween reversed if it is not selected
  return function(selected) {

    // Ternary operator. 
    // Store true in the reverse variable if menu is not selected
    // Get !tween.reversed() (This means true if tween is not reversed or false if it is reversed) and store it in reversed variable.
    let reversed = selected !== menu ? true : !tween.reversed();

    // what is actually returned is not a reference to the animation itself but rather a function that controls the reversed state of the animation
    tween.reversed(reversed);
  }
}

Here's the steps of what happens when the JS is ran:

  1. The DOM elements are selected.
  2. The groups are looped through and an animation for each group that animates the height of that specific group is created. This animation will be re-used each time that the group is animated (i.e. a new animation will not be created, only the state of this animation will be changed — this is a good technique of animating efficiently).

    With that being said, unlike what might appear what is actually returned is not a reference to the animation itself but rather a function that controls the reversed state of the animation. It'd probably help to look at the .reversed() documentation.
  3. The menu items are looped through, adding a click event listener to each. The event listener for each menu item passes in that menu item to the function that was returned in the last step. Inside of the function (returned from the last step), if the menu item is the same as the one that is clicked, the reversed state for that animation is set to the opposite of what it currently is, usually being set to true, meaning the animation will play forwards. However, if it's the same one that's clicked and it's reversed state is already false this means that it's already opened or opening, so its reversed state will be changed false.

    For all other animations, the reversed state will be set to true, meaning it will play backwards if its progress is not 0. It will do nothing if its progress is 0.

Again if you find what’s happening in step 3 a bit confusing where it’s using this line: animations.forEach(animate => animate(selected)) you can use this instead:

const groups = gsap.utils.toArray(".accordion-group");
const menus = gsap.utils.toArray(".accordion-menu");
const animations = [];

groups.forEach(group => createAnimation(group));

menus.forEach(menu => {
  menu.addEventListener("click", () => toggleAnimation(menu));
});

function toggleAnimation(menu) {
  // Save the current state of the clicked animation
  const selectedReversedState = menu.animation.reversed();
  
  // Reverse all animations
  animations.forEach(animation => animation.reverse());
  
  // Set the reversed state of the clicked accordion element to the opposite of what it was before
  menu.animation.reversed(!selectedReversedState);
}

function createAnimation(element) {
  const menu = element.querySelector(".accordion-menu");
  const box  = element.querySelector(".accordion-content");
  
  gsap.set(box, { height: "auto"})
  
  // Keep a reference to the animation on the menu item itself
  const tween = gsap.from(box, { 
    height: 0, 
    duration: 0.5, 
    ease: "power1.inOut",
    reversed: true
  });
  
  menu.animation = tween;
  animations.push(tween);
}

Here’s a minimal demo just in case:

And then you can just add a bunch of fancy CSS to give it a bit of SASS to it (that was such a bad nerd joke) to make it look something like this.

Fin.

Subscribe to Kay's Logs

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe