JSFiddle - React, Tailwind, and code Playground

by HDL52

HTML

<script src="https://unpkg.co/gsap@3/dist/gsap.min.js"></script>
<div class="wrapper">
  <button id="toggle" class="hamburger" type="button" aria-label="打开菜单" aria-controls="nav" aria-expanded="false">
    <span class="hamburgerLine"></span>
  </button>
  <nav id="nav" class="nav" aria-hidden="true">
    <ul id="menu" class="menu">
      <li>Menu 01</li>
      <li>Menu 02</li>
      <li>Mnue 03</li>
    </ul>
  </nav>
  <main id="main" class="main">
    <p>主要内容</p>
    <p>主要内容</p>
    <p>主要内容</p>
    <p>主要内容</p>
    <p>主要内容</p>
  </main>
  <svg class="overlay" width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none">
    <path id="overlayPath" class="overlayPath" vector-effect="non-scaling-stroke" d="M 0 100 V 100 Q 50 100 100 100 V 100 z" />
  </svg>
</div>

SCSS

body {
  background-color: #cdbea5;
  color: #000;
  font-size: 2rem;
}

.wrapper {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.hamburger {
  position: fixed;
  top: 24px;
  right: 24px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 80px;
  height: 80px;
  border: none;
  border-radius: 100vmax;
  background-color: black;
  cursor: pointer;
}

.hamburger::before,
.hamburger::after,
.hamburger .hamburgerLine {
  position: absolute;
  display: block;
  width: 50%;
  height: 4px;
  border-radius: 100vmax;
  background-color: white;
}

.hamburger::before,
.hamburger::after {
  content: "";
}

.hamburger::before {
  transform: translateY(-16px);
}

.hamburger::after {
  transform: translateY(16px);
}

.hamburger[aria-expanded=true] .hamburgerLine {
  opacity: 0;
}

.hamburger[aria-expanded=true]::before {
  transform: rotate(30deg);
}

.hamburger[aria-expanded=true]::after {
  transform: rotate(-30deg);
}

.nav {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10;
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.nav[aria-hidden=true] {
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
}

.nav[aria-hidden=false] {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}

.menu {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2em;
  width: 100%;
  height: 100%;
  background: #b46e41;
  color: white;
}

.main {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  height: 200vh;
}

.overlay {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  width: 100%;
  height: 100vh;
  pointer-events: none;
}

JavaScript

const CONTENTS = {
  MAIN: document.getElementById("main"),
  FOOTER: document.getElementById("footer")
};
const NAV = document.getElementById("nav");
const MENU = document.getElementById("menu");
const TOGGLE = document.getElementById("toggle");
const OVERLAYPATH = document.getElementById("overlayPath");


let isAnimating = false;

function menuOpen() {
  if (isAnimating) return;
  isAnimating = true;
  gsap
    .timeline({
      onStart: () => {
        NAV.setAttribute("aria-hidden", "false");
        TOGGLE.setAttribute("aria-label", "打开菜单");
        gsap.to(TOGGLE, {
          autoAlpha: 0,
          scale: 0,
          duration: 0.1
        });
        gsap.set(MENU, {
          y: -100,
          autoAlpha: 0
        });
      },
      onComplete: () => {
        isAnimating = false;
      }
    })


    .set(OVERLAYPATH, {
      attr: {
        d: "M 0 0 V 0 Q 50 0 100 0 V 0 z"
      }
    })


    .to(
      OVERLAYPATH, {
        attr: {
          d: "M 0 0 V 50 Q 50 100 100 50 V 0 z"
        },
        ease: "power4.in",
        duration: 0.5
      },
      0
    )


    .to(OVERLAYPATH, {
      attr: {
        d: "M 0 0 V 100 Q 50 100 100 100 V 0 z"
      },
      ease: "power2",
      duration: 0.3
    })


    .to(
      [CONTENTS.MAIN, CONTENTS.FOOTER], {
        y: 100,
        opacity: 0,
        duration: 0.3,
        ease: "power3.in"
      },
      0.1
    )


    .set(OVERLAYPATH, {
      attr: {
        d: "M 0 100 V 0 Q 50 0 100 0 V 100 z"
      }
    })



    .to(OVERLAYPATH, {
      attr: {
        d: "M 0 100 V 50 Q 50 100 100 50 V 100 z"
      },
      duration: 0.3,
      ease: "power2.in"
    })


    .to(OVERLAYPATH, {
      attr: {
        d: "M 0 100 V 100 Q 50 100 100 100 V 100 z"
      },
      duration: 0.3,
      ease: "power4"
    })


    .to(
      MENU, {
        y: 0,
        autoAlpha: 1,
        duration: 0.5,
        ease: "power4",
        onStart: () => {
          TOGGLE.setAttribute("aria-expanded", "true");
      ...