ハンバーガーメニューの実装(React+Tailwind CSS)

2024/01/29に公開

概要

レスポンシブなハンバーガーメニューを実装しました。SEO的には弱いかもしれません。(シンプルなハンバーガーメニューなので、アニメーションなどはありません)

いざ、ハンバーガーメニューを実装しようと思って調べても、意外と情報がなかったので参考になればと思います。

完成形

画面幅を広げたり縮めたりすると切り替わります。

PC

タブレット、スマホ


解説

<ul>要素のlg:flexによる並び方向の切り替え

<ul>要素は標準では<li>要素を縦方向に並べます。スマホでメニュー項目は縦並びで良いのですが、PCでは横方向に並べたいので、<ul>をflex要素にします。

        <ul className="lg:flex select-none">

1項目めの<li>の使い方

スマホ版で横方向で並べた時に、1項目めの<li>をヘッダーバーとして利用しています。
PC版の場合は「ロゴマーク」を表示しています。スマホ版ではw-fullのため、ヘッダバーとして「ハンバーガーボタン」「ロゴマーク」「ログアウトボタン」をjustify-betweenで左端・中央・右端に並べています。

          <li>
            { /* PCの場合のみ表示する要素 */ }
            <NavLink
              to="/"
              className="
                hidden lg:flex items-center h-full px-2 transition duration-150 ease-in-out
                text-gray-800
                hover:text-gray-400
                focus:text-gray-400
              "
            >
              <img src="/img/header/icon.jpg" className="h-10" alt="ロゴマーク"/>
            </NavLink>

            { /* タブレット・スマホの場合のみ表示する要素(1リスト目にヘッダ情報を並べる) */ }
            <div className="lg:hidden flex items-center h-14 w-full justify-between">
              { /* ハンバーガーボタン */ }
              <div className="flex px-3">
                { !open &&
                  <svg onClick={onClickOpen} xmlns="http://www.w3.org/2000/svg" className="w-5" aria-hidden="true" focusable="false" data-prefix="fas" role="img" viewBox="0 0 448 512">
                    <path fill="currentColor" d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
                    ></path>
                  </svg>
                }
                { open &&
                  <svg onClick={onClickClose} xmlns="http://www.w3.org/2000/svg" className="w-5" viewBox="0 0 20 20" fill="currentColor">
                    <path fillRule="evenodd" d="M10 8.586l4.293-4.293a1 1 0 011.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 011.414-1.414L10 8.586z" clipRule="evenodd" />
                  </svg>
                }
              </div>

              { /* ロゴ */ }
              <div className="flex px-3 py-2">
                <NavLink to="/"><img src="/img/header/icon.jpg" className="h-10" alt="ロゴマーク"/></NavLink>
              </div>

              { /* ログアウトボタン */ }
              <div className="flex mr-2 w-12">
                {props.loginFlag &&
                  <Form method="post" action="/logout">
                    <button
                      type="submit"
                      data-mdb-ripple="true"
                      data-mdb-ripple-color="light"
                      className="inline-block px-0 py-1 text-center bg-white text-gray-500 font-medium text-xs leading-tight uppercase focus:outline-one focus:ring-0 transition ease-in-out"
                    >
                      <img src="/img/header/icon_logout.png" className="h-10 inline-block" alt="ログアウトの画像"/><br/>
                    </button>
                  </Form>
                }
              </div>
            </div>
          </li>

ソース

import { useState } from "react";
import { NavLink, Form } from "react-router-dom";

export default function Header(props) {
  const [open, setOpen] = useState(false);

  const onClickOpen = () => {
    setOpen(true);
  }

  const onClickClose = () => {
    setOpen(false);
  }

  return (
        <>
<header className="sticky top-0 z-10 shadow-md">
  <nav className="flex w-full justify-between bg-white lg:h-16 lg:px-6">
    <div className="flex items-center w-full">

      <div className="block w-full">
        { /* flexで横方向に配置 */ }
        <ul className="lg:flex select-none">
          <li>
            { /* PCの場合のみ表示する要素 */ }
            <NavLink
              to="/"
              className="
                hidden lg:flex items-center h-full px-2 transition duration-150 ease-in-out
                text-gray-800
                hover:text-gray-400
                focus:text-gray-400
              "
            >
              <img src="/img/header/icon.jpg" className="h-10" alt="ロゴマーク"/>
            </NavLink>

            { /* タブレット・スマホの場合のみ表示する要素(1リスト目にヘッダ情報を並べる) */ }
            <div className="lg:hidden flex items-center h-14 w-full justify-between">
              { /* ハンバーガーボタン */ }
              <div className="flex px-3">
                { !open &&
                  <svg onClick={onClickOpen} xmlns="http://www.w3.org/2000/svg" className="w-5" aria-hidden="true" focusable="false" data-prefix="fas" role="img" viewBox="0 0 448 512">
                    <path fill="currentColor" d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"
                    ></path>
                  </svg>
                }
                { open &&
                  <svg onClick={onClickClose} xmlns="http://www.w3.org/2000/svg" className="w-5" viewBox="0 0 20 20" fill="currentColor">
                    <path fillRule="evenodd" d="M10 8.586l4.293-4.293a1 1 0 011.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 011.414-1.414L10 8.586z" clipRule="evenodd" />
                  </svg>
                }
              </div>

              { /* ロゴ */ }
              <div className="flex px-3 py-2">
                <NavLink to="/"><img src="/img/header/icon.jpg" className="h-10" alt="ロゴマーク"/></NavLink>
              </div>

              { /* ログアウトボタン */ }
              <div className="flex mr-2 w-12">
                {props.loginFlag &&
                  <Form method="post" action="/logout">
                    <button
                      type="submit"
                      data-mdb-ripple="true"
                      data-mdb-ripple-color="light"
                      className="inline-block px-0 py-1 text-center bg-white text-gray-500 font-medium text-xs leading-tight uppercase focus:outline-one focus:ring-0 transition ease-in-out"
                    >
                      <img src="/img/header/icon_logout.png" className="h-10 inline-block" alt="ログアウトの画像"/><br/>
                    </button>
                  </Form>
                }
              </div>
            </div>
          </li>
          { /* メニュー内容は各画面サイズで共有する。 */ }
          { open && <MenuList className="lg:hidden"/> }
          <MenuList className="hidden lg:list-item"/>
        </ul>
      </div>
    </div>

    { /* PCの場合のみ、右端に表示する要素 */ }
    <div className="items-center shrink-0 hidden lg:flex">
      { !props.loginFlag && <NavLink to="/authlogin"><button className="self-center px-8 py-3 rounded">ログイン</button></NavLink> }
      { !props.loginFlag && <NavLink to="/accountnew"><button className="self-center px-8 py-3 font-semibold rounded text-gray-800 bg-yellow-400">新規登録</button></NavLink> }
      { props.loginFlag && <Form method="post" action="/logout"><button className="self-center px-4 py-3 rounded text-white bg-red-400">ログアウト</button></Form> }
    </div>
      
  </nav>

</header>
       </>
    );
}

function MenuList(props) {
  return (
    <>
    <li className={props.className}>
      <NavLink
        to="/"
        className="
          flex items-center px-4 transition duration-150 ease-in-out
          text-gray-800
          hover:text-gray-400 hover:underline
          focus:text-gray-400 focus:underline
          h-12 lg:h-full
          w-full lg:w-auto
          bg-gray-100 lg:bg-transparent
          border-2 focus:border-gray-800 lg:border-0
        "
      >HOME</NavLink>
    </li>
    <li className={props.className}>
      <NavLink
        to="/reservation"
        className="
          flex items-center px-4 transition duration-150 ease-in-out
          text-gray-800
          hover:text-gray-400 hover:underline
          focus:text-gray-400 focus:underline
          h-12 lg:h-full
          bg-white lg:bg-transparent
          border-2 border-t-0 lg:border-0
        "
    >予約</NavLink>
    </li>
  〜省略〜
  </>
  );
}

最後に

ハンバーガーメニューは色々な実装方法があると思います。
こちらに書かれている内容がベストプラクティスではないと思いますので、ご参考までに。

Discussion