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} />
あとは、ToggleButtonのonClick
にtoggleFunction
を登録し、open
の状態がToggleButtonとNavigationに渡されるようにするだけです。
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-expanded
やaria-hidden
、aria-controls
などの属性を使って、このボタンを押せば何が起こるのか・・このボタンと対応するコンテンツが今それぞれどういう状態なのか・・みたいな内部的な部分と視覚的な部分がリンクしていると、単純に気持ちいいなーと思います。
アクセシビリティに関しては、非常に深い話になると思うので、とりあえず「気持ちいいなー」を大切にしてコードを書けば、見た目を作るだけのコードではなく、意味のあるコードが書けるのかなーと考えながら精進しています。