Burger Menu Button

A GSAP-animated burger-to-close button. The three lines animate in a multi-step sequence: the middle line shrinks, the outer lines exit, instantly reposition at crossed angles, then animate into an X. Closing reverses all lines back to their original state in one smooth tween.

gsapbuttonmenuburgeranimationtoggle

Setup — External Scripts

Setup: External Scripts
html
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/CustomEase.min.js"></script>

Code

index.html
html
<button data-menu-button="burger" class="menu-button">
  <div class="menu-button-line"></div>
  <div class="menu-button-line"></div>
  <div class="menu-button-line"></div>
</button>
styles.css
css
.menu-button {
  grid-column-gap: .1875em;
  grid-row-gap: .1875em;
  flex-flow: column;
  padding: 1em;
  font-size: 1em;
  display: flex;
  background: transparent;
  -webkit-appearance: none;
  border: none;
}

.menu-button-line {
  background-color: #e7dddb;
  width: 2em;
  height: .1875em;
}
script.js
javascript
gsap.registerPlugin(CustomEase)
CustomEase.create("button-ease", "0.5, 0.05, 0.05, 0.99")

function initMenuButton() {
  // Select elements
  const menuButton = document.querySelector("[data-menu-button]");
  const lines = document.querySelectorAll(".menu-button-line");
  const [line1, line2, line3] = lines;

  // Define one global timeline
  let menuButtonTl = gsap.timeline({
    defaults: {
      overwrite: "auto",
      ease: "button-ease",
      duration: 0.3
    }
  })

  const menuOpen = () => {
    menuButtonTl.clear() // Stop any previous tweens, if any
    .to(line2, { scaleX: 0, opacity: 0 }) // Step 1: Hide middle line
    .to(line1, { x: "-1.3em", opacity: 0 }, "<") // Step 1: Move top line
    .to(line3, { x: "1.3em", opacity: 0 }, "<") // Step 1: Move bottom line
    .to([line1, line3], { opacity: 0, duration: 0.1 }, "<+=0.2") // Step 2: Quickly fade top and bottom lines
    .set(line1, { rotate: -135, y: "-1.3em", scaleX: 0.9 }) // Step 3: Instantly rotate and scale top line
    .set(line3, { rotate: 135, y: "-1.4em", scaleX: 0.9 }, "<") // Step 3: Instantly rotate and scale bottom line
    .to(line1, { opacity: 1, x: "0em", y: "0.5em" }) // Step 4: Move top line to final position
    .to(line3, { opacity: 1, x: "0em", y: "-0.25em" }, "<+=0.1"); // Step 4: Move bottom line to final position
  }

  const menuClose = () => {
    menuButtonTl.clear() // Stop any previous tweens, if any
    .to([line1, line2, line3], { // Move all lines back in a single animation
      scaleX: 1,
      rotate: 0,
      x: "0em",
      y: "0em",
      opacity: 1,
      duration: 0.45,
      overwrite: "auto",
    })
  }

  // Toggle Animation
  menuButton.addEventListener("click", () => {
    const currentState = menuButton.getAttribute("data-menu-button");

    if (currentState === "burger") {
      menuOpen()
      menuButton.setAttribute("data-menu-button", "close");
    } else {
      menuClose()
      menuButton.setAttribute("data-menu-button", "burger");
    }
  });
}

// Initialize Burger Menu Button
document.addEventListener('DOMContentLoaded', () => {
  initMenuButton();
});

Guide

Open Sequence

The open animation runs in 4 steps: (1) middle line shrinks and outer lines exit in opposite directions, (2) outer lines quickly fade out, (3) they instantly reposition at ±135° rotations and negative Y offsets via gsap.set, (4) they animate back to center, forming an X.

Close Sequence

The close animation is a single tween that resets all three lines — scale, rotation, x, y, and opacity — back to their original values in one smooth motion.

State Tracking

The data-menu-button attribute toggles between "burger" and "close" on each click, driving both the animation direction and providing a CSS hook if you need to style other elements based on menu state.

Timeline Management

menuButtonTl.clear() kills all pending tweens before starting a new sequence, so rapid clicking never causes the animations to stack or fight each other.

Custom Ease

The "button-ease" CustomEase curve (0.5, 0.05, 0.05, 0.99) gives a fast start that decelerates sharply at the end, producing a snappy, mechanical feel. Adjust the four cubic-bezier values to change the character of the animation.