Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
26
Help us understand the problem. What is going on with this article?
@kohei_abe

Next.jsでイケてるハンバーガーメニューを作ろう!

対象

  • JSでハンバーガーメニューは作れるけどReactでどうやったらいいかわからない
  • イケてるメニューボタンが作りたい

事前知識

  • html, css, js, React, Next.jsの基礎

準備

環境構築からいきましょう!

npm init next-app menu-button
cd menu-button
mkdir components
cd components
mkdir header
cd header
touch index.jsx
touch index.module.scss

環境構築してcomponentsファイルを作り、headerコンポーネントを作ります。
Sassもインストールしときましょうか

npm install sass

これで準備おっけいです!
今回作るハンバーガーメニューはこちら!!
スクリーンショット 2021-02-10 21.10.04.png

スクリーンショット 2021-02-10 21.10.28.png
スクリーンショット 2021-02-10 21.09.32.png

1024pxからメニューボタンに変わるイメージです!!
ちょっと普通に作るよりカッコよくしてみました。
ハンバーガーメニューってよく多用されてるイメージですが、実はUI的にはよくないと言われてるみたいです。

いろんなサイトのメニューボタンをみて感性を磨きましょう♪

実装

今回はReact hooksのuseStateを使っていきます!!

components/header/index.jsx
import styles from "./index.module.scss";
import Link from "next/link";
import React, {useState} from "react"
export default function Header() {
  const [openMenu, setOpenMenu] = useState(false);
  const menuFunction = () => {
    setOpenMenu(!openMenu);
  }
  return (
    <React.Fragment>
      <header id="header" className={styles.header}>
        <div className={styles.logo}>
          <Link href="/">
            <a className={styles.logo}>ハンバーガー♡</a>
          </Link>
        </div>
        <nav>
          <ul>
            <li>
              <Link href="/">
              <a>メニュー</a>
              </Link>
            </li>
            <li>
              <Link href="/">
              <a>メニュー</a>
              </Link>
            </li>
            <li>
              <Link href="/">
              <a>メニュー</a>
              </Link>
            </li>
            <li>
              <Link href="/">
              <a>メニュー</a>
              </Link>
            </li>
            <li>
              <Link href="/">
              <a>メニュー</a>
              </Link>
            </li>
            <li>
              <Link href="/">
                <a>メニュー</a>
              </Link>
            </li>
          </ul>
        </nav>
        <div className={styles.container}>
          <div className={styles.humburger} onClick={() => menuFunction()}>
            <span className={openMenu ? styles.open : undefined}></span>
            <span className={openMenu ? styles.open : undefined}></span>
            <p className={openMenu ? styles.open : undefined}>Menu</p>
          </div>
        </div>
      </header>
      <div className={`${styles.drawerMenu} ${openMenu ? styles.open : undefined}`}>
        <ul>
          <div className={styles.close} onClick={() => menuFunction()}>
            <span></span>
            <span></span>
            <p>Close</p>
          </div>
          <li>
            <Link href="/">
              <a>
                <p className={styles.mainTitle}>メニュー</p>
                <p className={styles.subTitle}>私のメニュー</p>
              </a>
            </Link>
          </li>
          <li>
            <Link href="/">
            <a>
              <p className={styles.mainTitle}>メニュー</p>
              <p className={styles.subTitle}>私のメニュー</p>
            </a>
            </Link>
          </li>
          <li>
            <Link href="/">
            <a>
              <p className={styles.mainTitle}>メニュー</p>
              <p className={styles.subTitle}>私のメニュー</p>
            </a>
            </Link>
          </li>
          <li>
            <Link href="/">
            <a>
              <p className={styles.mainTitle}>メニュー</p>
              <p className={styles.subTitle}>私のメニュー</p>
            </a>
            </Link>
          </li>
          <li>
            <Link href="/">
            <a>
              <p className={styles.mainTitle}>メニュー</p>
              <p className={styles.subTitle}>私のメニュー</p>
            </a>
            </Link>
          </li>
          <li>
            <Link href="/contact">
            <a>
              <p className={styles.mainTitle}>メニュー</p>
              <p className={styles.subTitle}>私のメニュー</p>
            </a>
            </Link>
          </li>
        </ul>
      </div>
    </React.Fragment>
  )
}

説明していきます!
まず上からコンポーネントを読み込んでいます。
sytelsはscssを読み込んでいるイメージです。
Linkコンポーネントを読み込み
import React, {useState} from "react"でReact hooksのuseStateが読み込めるようになります!

useStateは簡単に説明すると
[変数, 状態を変化させる関数] = useState(初期値)
って感じです。
一般的に関数はset変数名で書くのを見かけることが多いです!!

この場合は初期値がfalseになっているのでこれを状態繊維させたいときは
setOpenMenu(true)って感じですね!
これでfalseからtrueになります!!

つまりその下に書いてある関数menuFunctionの
setOpenMenu(!openMenu)はtrueとfalseを繊維させる意味をしています!
先頭に!をつけると簡単にいうと"〜じゃない"って感じになります。
trueじゃない = false
falseじゃない = true
って感じですね!!

ハンバーガーメニューは開いてるか(true)閉じているか(false)しかないので、trueとfalseの真偽知で示すことができます!
初めの状態は閉じていて欲しいのでfalseを初期値にします!

あとはメニューが開いている状態を.openクラスに渡して、trueならばクラスを渡すと言った記述をします!

  <div className={styles.humburger} onClick={() => menuFunction()}>
     <span className={openMenu ? styles.open : undefined}></span>
     <span className={openMenu ? styles.open : undefined}></span>
     <p className={openMenu ? styles.open : undefined}>Menu</p>
  </div>

クリックイベントmenuFunctionを発火させ、
openMenu ? styles.open : undefindは
三項演算子です!わからない方はぐぐりましょう!
openMenuがfalseのときはクラスを何も渡さないのでundefindを渡しています!!

スタイリングしましょう!!

components/header/index.module.scss
  padding: 0 40px;
  z-index: 50;
  position: fixed;
  .logo {
    font-size: 20px;
  }

  nav {
    margin-left: auto;
  }
  li {
    display: inline-block;
    margin-right: 15px;

    &:nth-child(6) {
      background-color: rgb(34, 35, 35);
      color: white;
      width: 90px;
      padding: 5px 10px;
      margin-right: 0;
    }
  }
}

@media (max-width: 1024px) {
  .header {
    position: initial;
    nav {
      display: none;
    }
  }
}

.humburger {
  background-color: rgb(34, 35, 36);
  color: white;
  padding: 10px 5px 0 5px;
  display: none;
  cursor: pointer;
  position: fixed;
  top: 20px;
  right: 40px;
  span {
    background-color: white;
    width: 30px;
    height: 1px;
    display: block;
    margin-bottom: 7px;
    &:nth-child(1) {
      width: 20px;
      margin-left: 10px;
    }
  }
  p {
    font-size: 13px;
    margin-top: -4px;
    margin-bottom: 5px;
  }
}
.drawerMenu {
  width: 0;
  color: white;
  background-color: rgb(34, 35, 36);

  transition: 0.5s;
  height: 0;
  opacity: 0;
  z-index: -100;
  &.open {
    height: 100vh;
    opacity: 1;
    z-index: 100;
  }
}

@media (max-width: 1024px) {
  .header {
    padding: 0 20px;
    .logo {
      margin-top: 25px;
    }
  }
  .container {
    margin: 0 0 0 auto;
    .humburger {
      display: block;
    }
  }
  .drawerMenu {
    width: 100%;
    position: fixed;
    top: 0;
    ul {
      width: 90%;
      margin: 0 auto;
      li {
        margin-bottom: 25px;
        opacity: 0.8;
        font-family: "Josefin Sans", sans-serif;
        font-weight: 200;
      }
    }
  }
  .mobileLogo {
    width: 250px;
    height: 80px;
    display: inline-block;
    position: absolute;
    top: 0px;
  }
  .mainTitle {
    font-size: 28px;
    margin-bottom: 5px;
    letter-spacing: 2px;
  }
  .subTitle {
    font-size: 15px;
  }
  .close {
    cursor: pointer;
    margin-top: 28px;
    padding-right: 5px;
    span {
      background-color: white;
      width: 30px;
      height: 1px;
      display: block;
      margin-bottom: 10px;
      margin: 0 0 10px auto;
      &:first-child {
        transform: translateY(6px) rotate(150deg);
      }
      &:nth-child(2) {
        transform: translateY(-6px) rotate(-150deg);
        margin-bottom: 0;
      }
    }
    p {
      text-align: right;
      font-size: 11px;
      padding-right: 1px;
    }
  }
}
@media (max-width: 600px) {
  .header {
    padding: 0 20px;
  }
  .humburger {
    top: 15px;
    right: 20px;
  }
  .mobileLogo {
    width: 190px;
    height: 60px;
    top: 10px;
  }
}
@media (max-width: 340px) {
  .header {
    padding: 0 10px;
    .logo {
      width: 170px;
      height: 60px;
    }
  }
}

.drawerMenu {
width: 0;
color: white;
background-color: rgb(34, 35, 36);

transition: 0.5s;
height: 0;
opacity: 0;
z-index: -100;
&.open {
height: 100vh;
opacity: 1;
z-index: 100;
}
}
ここが重要です。
drowerMenuを閉じている状態では、widthとheightとopacityを0にして見えない状態にします!
クリックイベントがされると
heightが100vhに
100vhは簡単にいうと画面いっぱいにって感じです。
opacityとz-indexが付与されて
表示されてきます♪
transitionをつけると少しアニメーションをつけることができます!!

お疲れ様でした!!

こちら参考のgithubのコードです。
https://github.com/abeshi03/menu-botton-next/tree/main/components/header

26
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
kohei_abe
自社開発企業でエンジニアをやっています!主にNext.js, Vue.js, Nuxt.jsのフロントエンドを担当しています。 AWSも勉強中

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
26
Help us understand the problem. What is going on with this article?