KOKONIARUKOTO.

React

Reactで作るハンバーガーメニュー

デモ

今回は、下記のデモを参考に進めていきます。

デモ:React ハンバーガーメニュー

サンプルなので、最低限のコードとコンポーネントで挙動を確認できるものにしています。

一応、実用性も考慮してTypeScript(.tsx)で書いてあります。

コンポーネント

今回のサンプルでは、以下の3つのコンポーネントに分けています。

  • ・Header(コンテナ)
  • ・ToggleButton(ボタン)
  • ・Navigation(コンテンツ)

以降は、それぞれのコンポーネント毎に解説していきます。

Header(コンテナ)

ハンバーガーメニューはヘッダー内にあることが多いと思うので、ボタンとコンテンツを囲むコンポーネントとしてHeader.tsxを作ります。

// Header.tsx

import { FC, useState } from "react";
import { ToggleButton } from "./ToggleButton";
import { Navigation } from "./Navigation";
import "./styles.css";

const Header: FC = () => {
  const [open, setOpen] = useState(false);
  const toggleFunction = () => {
    setOpen((prevState) => !prevState);
  };

  return (
    <header className="header">
      <ToggleButton
        open={open}
        controls="navigation"
        label="メニューを開きます"
        onClick={toggleFunction}
      />
      <Navigation id="navigation" open={open} />
    </header>
  );
};
export default Header;

ハンバーガーメニューの状態を管理するために、ステートフックというReactの機能を使って、状態の更新を行います。

状態の初期値とクリック時の処理

import { FC, useState } from "react";

まずは、useStateをインポートし、ボタンをクリックする度にボタンとメニューのコンポーネントの状態を更新する記述をします。

const [open, setOpen] = useState(false);

ここでは、openというstate変数に初期値として、falseをセットしています。

setOpenの部分は、openを更新する関数です。

今回のケースでは、ボタンをクリックする度にsetOpenの関数(ここではtoggleFunction)が走り、openの状態が更新されます。

const toggleFunction = () => {
  setOpen((prevState) => !prevState);
};

今回は、真偽を入れ替えるだけの処理でいいので、prevState(現在の状態)を反転させるだけの関数を記述します。

<ToggleButton
  open={open}
  controls="navigation"
  label="メニューを開きます"
  onClick={toggleFunction}
/>
<Navigation id="navigation" open={open} />

あとは、ToggleButtononClicktoggleFunctionを登録し、openの状態がToggleButtonNavigationに渡されるようにするだけです。

ToggleButton(ボタン)

ボタンの体裁は毎回異なると思いますが、openの状態でaria-expandedの真偽を切り替えてスタイルが変わるようにします。

// ToggleButton.tsx

import { FC, MouseEventHandler } from "react";
import "./styles.css";

type Props = {
  open: boolean;
  onClick: MouseEventHandler;
  controls: string;
  label: string;
};

export const ToggleButton: FC<Props> = ({ open, controls, label, onClick }) => {
  return (
    <button
      type="button"
      aria-controls={controls}
      aria-expanded={open}
      aria-label={label}
      onClick={onClick}
      className="toggleButon"
    >
      <span className="line-1"></span>
      <span className="line-2"></span>
    </button>
  );
};

aria-expanded={open}{open}(初期値はfalse)がクリックの度に更新されるので、それに合わせてスタイルを当てておきます。

/* styles.css */

.line-1 {
  transform: translate(0, -10px);
}

.line-2 {
  transform: translate(0, 10px);
}

.toggleButon[aria-expanded="true"] > .line-1 {
  transform: translate(0, 0) rotate(-45deg);
}

.toggleButon[aria-expanded="true"] > .line-2 {
  transform: translate(0, 0) rotate(45deg);
}

Navigation(コンテンツ)

コンテンツに関しても、ボタンのようにaria-hiddenの真偽を切り替えて表示を操作するだけです。

// Navigation.tsx

import { FC } from "react";
import "./styles.css";

type Props = {
  open: boolean;
  id: string;
};

export const Navigation: FC<Props> = ({ open, id }) => {
  return (
    <nav id={id} aria-hidden={!open} className="navigation">
      <ul>
        <li>about</li>
        <li>works</li>
        <li>contact</li>
      </ul>
    </nav>
  );
};
/* styles.css */

.navigation {
  display: none;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 5rem 2rem;
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  height: 100vh;
  background-color: white;
}

.navigation[aria-hidden="false"] {
  display: flex;
}

アクセシビリティも大切にしたい

ハンバーガーメニューやタブなどの表示、非表示を切り替えるような機能の実装は、プロダクトや目的によって変わってきますが・・

今回のように、aria-expandedaria-hiddenaria-controlsなどの属性を使って、このボタンを押せば何が起こるのか・・このボタンと対応するコンテンツが今それぞれどういう状態なのか・・みたいな内部的な部分と視覚的な部分がリンクしていると、単純に気持ちいいなーと思います。

アクセシビリティに関しては、非常に深い話になると思うので、とりあえず「気持ちいいなー」を大切にしてコードを書けば、見た目を作るだけのコードではなく、意味のあるコードが書けるのかなーと考えながら精進しています。

この記事をシェア

最新の記事

API

fetchを使って楽天商品検索APIから商品情報を取得する

fetchを使って楽天商品検索APIから商品情報を取得する

ガジェット

エンジニアやWEBデザイナーにおすすめのWindows搭載PCとスペック

エンジニアやWEBデザイナーにおすすめのWindows搭載PCとスペック

API

Rakuten Webserviceにアプリ登録して楽天APIを使えるようにする

Rakuten Webserviceにアプリ登録して楽天APIを使えるようにする

もっと見る

KOKONIARUKOTO.

ADRESS
〒730-0814
広島市中区羽衣町3-19
TEL / FAX
082-847-6488
082-847-6489
MAIL
info@kokoniarukoto.dev
メールフォームはこちら