【React】ヘッダーにMaterial UI(MUI)でハンバーガーメニューを実装|ポートフォリオサイトの作り方

作成日: 更新日:

開発環境

  • Visual Studio Code:version 1.73.0
  • OS:Windows10
  • Node.js:v18.14.0
  • npm:9.3.1
  • react:18.2.0
  • react-dom:18.2.0
  • react-router-dom:6.8.2
  • typescript:4.9.5
  • mui:5.11.10
  • mui/icons-material:5.11.11

ヘッダーにMaterial UI(MUI)でハンバーガーメニューを実装する手順

ヘッダーにMaterial UI(MUI)でハンバーガーメニューを実装する手順を解説していきます。

Material UI(MUI)のアイコンをインストール

まずはMaterial UI(MUI)のアイコンをインストールします。

1npm install @mui/icons-material

Material UI v5 では、アイコンの使用方法が以前のバージョンと異なっています。 以前のバージョンではSVG アイコンでしたが、v5からはFont Awesome アイコンが採用されており、必要なアイコンコンポーネントをインポートすることによってコンポーネント内で使用することができます。

ヘッダーのナビゲーションバーをレスポンシブにする

次にヘッダーのナビゲーションバーをレスポンシブにしていきます。

1// react-portfolio-app/src/components/Header.tsx
2import React from "react";
3import { Link } from "react-router-dom";
4import AppBar from "@mui/material/AppBar";
5import Container from "@mui/material/Container";
6import Box from "@mui/material/Box";
7import List from "@mui/material/List";
8import ListItem from "@mui/material/ListItem";
9import ListItemButton from "@mui/material/ListItemButton";
10import ListItemText from "@mui/material/ListItemText";
11import Typography from "@mui/material/Typography";
12import Logo from "../assets/images/logo.png"
13
14+ import MenuIcon from '@mui/icons-material/Menu';
15
16const setNavLinks: Array<{ text: string, url: string }> = [
17    { text: "Top", url: "/" },
18    { text: "Profile", url: "/profile" },
19    { text: "Skill", url: "/skill" },
20    { text: "Production", url: "/production" },
21    { text: "Contact", url: "/contact" },
22    { text: "Blog", url: "/blog" },
23];
24
25const Header: React.FC = () => {
26  return(
27    <>
28        <AppBar component="header" position="static">
29            <Container maxWidth="md">
30                <Box sx={{ display: 'flex', justifyContent: 'space-between'}}>
31                    <Box>
32                        <Typography component="h1">
33                            <Link to="/">
34                                <img src={Logo} alt="Webエンジニアのポートフォリオサイト" height="60" width="auto" />
35                            </Link>
36                        </Typography>
37                    </Box>
38                    <Box>
39                        <List component="nav" sx={{ display: 'flex', justifyContent: 'flex-start' }}>
40+                             <ListItem disablePadding>
41+                                 <ListItemButton sx={{ textAlign: 'center', display: { xs: 'block', md: 'none' } }}>
42+                                     <ListItemText primary={<MenuIcon />} />
43+                                 </ListItemButton>
44+                             </ListItem>
45+                             { setNavLinks.map( (navLink) => (
46+                             <ListItem disablePadding>
47+                                 <ListItemButton sx={{ textAlign: 'center', display: { xs: 'none', md: 'block' } }} component=+ {Link} to={navLink.url}>
48+                                     <ListItemText primary={navLink.text} />
49+                                 </ListItemButton>
50+                             </ListItem>
51                            ))}
52                        </List>
53                    </Box>
54                </Box>
55            </Container>
56        </AppBar>
57    </>
58  );
59};
60
61export default Header;

まずはメニュー用のハンバーガーアイコンをインポートしておきます。 そしてハンバーガーメニューのボタンはmdサイズで非表示、xsサイズで表示されるようにdisplayのプロパティを割り当てていきます。 また、ナビゲーション部分に関しては逆にxsサイズで非表示、mdサイズで表示されるようにdisplayのプロパティを割り当てることにより、レスポンシブに切り替えることができます。

useStateによる状態管理をする

次にuseStateによる状態管理をするためのメソッドを定義していきます。

1// react-portfolio-app/src/components/Header.tsx
2
3import MenuIcon from '@mui/icons-material/Menu';
4+ import { useState } from "react";
5
6const setNavLinks: Array<{ text: string, url: string }> = [
7    { text: "Top", url: "/" },
8    { text: "Profile", url: "/profile" },
9    { text: "Skill", url: "/skill" },
10    { text: "Production", url: "/production" },
11    { text: "Contact", url: "/contact" },
12    { text: "Blog", url: "/blog" },
13];
14
15const Header: React.FC = () => {
16+     const [open, setOpen] = useState(false);
17+
18+     const handleDrawerOpen = () => {
19+         setOpen(true);
20+     }
21+
22+     const handleDrawerClose = () => {
23+         setOpen(false);
24+     }
25
26  return(

useStateをインポートしておきます。 「open,setOpen」という変数の初期値はfalseですが、handleDrawerOpenでは「setOpen」をtrue、handleDrawerCloseでは「setOpen」をfalseにすることにより、ハンバーガーメニューの開閉を管理します。

ハンバーガーメニューのドロワー部分を作成

次にハンバーガーメニューのドロワー部分を作成します。

1// react-portfolio-app/src/components/Header.tsx
2import React from "react";
3import { Link } from "react-router-dom";
4import AppBar from "@mui/material/AppBar";
5import Container from "@mui/material/Container";
6import Box from "@mui/material/Box";
7import List from "@mui/material/List";
8import ListItem from "@mui/material/ListItem";
9import ListItemButton from "@mui/material/ListItemButton";
10import ListItemText from "@mui/material/ListItemText";
11import Typography from "@mui/material/Typography";
12import Logo from "../assets/images/logo.png"
13
14import MenuIcon from '@mui/icons-material/Menu';
15+ import CloseIcon from '@mui/icons-material/Close';
16import { useState } from "react";
17+ import Drawer from "@mui/material/Drawer";
18
19const setNavLinks: Array<{ text: string, url: string }> = [
20    { text: "Top", url: "/" },
21    { text: "Profile", url: "/profile" },
22    { text: "Skill", url: "/skill" },
23    { text: "Production", url: "/production" },
24    { text: "Contact", url: "/contact" },
25    { text: "Blog", url: "/blog" },
26];
27
28const Header: React.FC = () => {
29    const [open, setOpen] = useState(false);
30
31    const handleDrawerOpen = () => {
32        setOpen(true);
33    }
34
35    const handleDrawerClose = () => {
36        setOpen(false);
37    }
38
39  return(
40    <>
41        <AppBar component="header" position="static">
42            <Container maxWidth="md">
43                <Box sx={{ display: 'flex', justifyContent: 'space-between'}}>
44                    <Box>
45                        <Typography component="h1">
46                            <Link to="/">
47                                <img src={Logo} alt="Webエンジニアのポートフォリオサイト" height="60" width="auto" />
48                            </Link>
49                        </Typography>
50                    </Box>
51                    <Box>
52                        <List component="nav" sx={{ display: 'flex', justifyContent: 'flex-start' }}>
53                            <ListItem disablePadding>
54+                                 <ListItemButton onClick={handleDrawerOpen} sx={{ textAlign: 'center', display: { xs: 'block', md: 'none' } }}>
55                                    <ListItemText primary={<MenuIcon />} />
56                                </ListItemButton>
57                            </ListItem>
58                            { setNavLinks.map( (navLink) => (
59                            <ListItem disablePadding>
60                                <ListItemButton sx={{ textAlign: 'center', display: { xs: 'none', md: 'block' } }} component={Link} to={navLink.url}>
61                                    <ListItemText primary={navLink.text} />
62                                </ListItemButton>
63                            </ListItem>
64                            ))}
65                        </List>
66                    </Box>
67+                     <Drawer anchor="right" open={open} onClose={handleDrawerClose} PaperProps={{ style: { width: '100%' } }}>
68+                         <List component="nav" sx={{ display: 'block', justifyContent: 'normal' }}>
69+                             <ListItem disablePadding>
70+                                 <ListItemButton onClick={handleDrawerClose} sx={{ textAlign: 'center', borderBottom: "solid 1px #696969" }}>
71+                                     <ListItemText primary={<CloseIcon />} />
72+                                 </ListItemButton>
73+                             </ListItem>
74+                             { setNavLinks.map( (navLink) => (
75+                             <ListItem disablePadding>
76+                                 <ListItemButton onClick={handleDrawerClose} sx={{ textAlign: 'center', borderBottom: "solid 1px #696969" }} component={Link} to={navLink.url}>
77+                                     <ListItemText primary={navLink.text} />
78+                                 </ListItemButton>
79+                             </ListItem>
80+                             ))}
81+                         </List>
82+                     </Drawer>
83                </Box>
84            </Container>
85        </AppBar>
86    </>
87  );
88};
89
90export default Header;

閉じるボタンのアイコンとMaterial UI(MUI)のDrawerをインポートします。 まずはクリックされたら「setOpne」がtureになるようクリックイベントにはhandleDrawerOpenを割り当てておきます。 そしてMaterial UI(MUI)のDrawer内にナビゲーション部分を作成し、クリックされたら「setOpne」がfalseになるようhandleDrawerCloseを割り当てておきます。 あとはDrawerのopenプロパティに割り当てたopenという変数がtrueとfalseで切り替わることにより、ドロワーの開閉を管理することができます。

おわりに

ReactでヘッダーにMaterial UI(MUI)でハンバーガーメニューを実装するための手順を解説してきましたが、いかがだったでしょうか。 サイト訪問者に気持ちよくサイトをみてもらにヘッダーのナビゲーションはとても重要であり、ナビゲーションのデザインや仕様にこだわっているエンジニアもかなり多いかと思います。 是非、魅力的なヘッダーのナビゲーションを作っていきましょう。

【React】ヘッダーにMaterial UI(MUI)でハンバーガーメニューを実装|ポートフォリオサイトの作り方 | いっしー@Webエンジニア