スライドしながら出てくるメニュー

クリックGSAPリスト

メニューを開くと左からアイテムが出現してくるアニメーションです。

実装のポイント

動きはスライドしながら入ってくるリストを応用したものですが、今回は動きの実装にGSAPというアニメーションライブラリを使用しています。

いくつかの要素の動きを統合するようなアニメーションにおいてはGSAPは非常に強力なツールです。以前はライセンスが少し複雑で使用場面が限られるところもありましたが、ライセンスが大きく緩和されて特定の用途以外では自由に使えるようになりました。

ここではGSAPそのものの使い方には言及しませんが、GSAPのtimeline機能を使ってメニューのコンテナー.menuContainer要素の開閉とアイテム.sampleItem要素の出現が協調的に動くようなアニメーションにしています。

コード例

HTML

<div class="menuContainer">
  <button class="menuButton">
    <img src="/assets/icon_more.svg" alt="" width="24" height="24" />
  </button>
  <ul class="slideInMenu">
    <li class="sampleItem">
      <img
        class="menuIcon"
        src="/assets/icon_home.svg"
        alt=""
        width="24"
        height="24"
      />
      <p class="iconName">Home</p>
    </li>
    <li class="sampleItem">
      <img
        class="menuIcon"
        src="/assets/icon_apps.svg"
        alt=""
        width="24"
        height="24"
      />
      <p class="iconName">Apps</p>
    </li>
    <li class="sampleItem">
      <img
        class="menuIcon"
        src="/assets/icon_camera.svg"
        alt=""
        width="24"
        height="24"
      />
      <p class="iconName">Camera</p>
    </li>
    <li class="sampleItem">
      <img
        class="menuIcon"
        src="/assets/icon_settings.svg"
        alt=""
        width="24"
        height="24"
      />
      <p class="iconName">Settings</p>
    </li>
  </ul>
</div>

CSS

:root{

}
.slideInMenu {
  display: flex;
  list-style: none;
  gap: 12px;
  padding-right: 16px;
}

.menuContainer {
  position: relative;
  border: 2px solid var(--color-text);
  border-radius: 32px;
  width: 64px;
  height: 64px;
  padding: 8px 8px 8px 52px;
  overflow: hidden;
}

.menuButton {
  position: absolute;
  top: 50%;
  left: 6px;
  margin-top: -24px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  z-index: 1;
}

.sampleItem {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  opacity: 0;
}

.menuIcon {
  width: 24px;
  height: 24px;
  display: block;
}

.iconName {
  font-size: 12px;
}

JavaScript

import { gsap } from "gsap";
const menuButton = document.querySelector(".menuButton");
const menuContainer = document.querySelector(".menuContainer");

const openTimeline = gsap.timeline({
  paused: true,
  onComplete: () => {
    isAnimating = false;
  },
});
const closeTimeline = gsap.timeline({
  paused: true,
  onComplete: () => {
    isAnimating = false;
  },
});

let isAnimating = false;

openTimeline
  .to(".menuContainer", {
    width: "auto",
    padding: "8px 8px 8px 52px",
    duration: 0.8,
    ease: "power4.out",
  })
  .fromTo(
    ".sampleItem",
    {
      opacity: 0,
      xPercent: -80,
    },
    {
      opacity: 1,
      duration: 0.8,
      ease: "power3.out",
      xPercent: 0,
      stagger: 0.07,
    },
    "<+=0.15",
  );

closeTimeline
  .to(".sampleItem", {
    opacity: 0,
    xPercent: -40,
    duration: 0.4,
    ease: "power4.out",
  })
  .to(
    ".menuContainer",
    {
      width: "64px",
      padding: "8px",
      duration: 0.6,
      ease: "power3.out",
    },
    "<+=0.05",
  );

menuButton.addEventListener("click", () => {
  if (isAnimating) {
    return;
  }
  isAnimating = true;
  menuContainer.classList.toggle("isActive");
  if (menuContainer.classList.contains("isActive")) {
    openTimeline.restart();
  } else {
    closeTimeline.restart();
  }
});
Copyright Web Motion Catalog all rights reserved.