【コピペだけ!】高機能ハンバーガーメニュー完全レスポンシブ対応(React & Sass 使用)
プログラミング
目次
タブレット、スマホサイズで、ハンバーガーメニューが必要なことが多いとは思いますが実装が面倒ですよね。
Tailwindを使ったハンバーガーメニューはこちら⬇︎
今回はコピペするだけで、完全レスポンシブ対応のハンバーガーメニューのコードを紹介します。
React.js & Sassを使っていて、TypeScript の tsx ファイルにも対応しています。
この記事のコードを使うことで、簡単にハンバーガーメニューを実装できるかと思います。
完全に好みですがCSSのアニメーションを使ってレインボータイトルも実装しています!
注意:これらのコードは 2024年2月の時点でのコードです。(下記機能がプロジェクトに沿う場合のみお使いください。)
このハンバーガーメニューの機能一覧です:
- 完全レスポンシブ対応
- 下にスクロールしたら、メニュー全体に透明が少しかかる
- タイトルはCSSのアニメーションでレインボーに輝く
- メニュー内の TEST は、URLが “/contact” と “/about” の時は非表示になる
- リンクタグを押した時に画面上部にスクロール
- Fixed Header
使用したライブラリー
- React.js (VITE)
- react-touter-dom
- sass
- react-icons
ファイル構成
src
-- App.tsx
-- components folder
-- Header.tsx
-- MenuSp
-- sass
-- main.scss
-- _setting.scss
-- _Hamburger.scss
-- _Header.scss
-- _CommonParts.scss
React VITE と各ライブラリーのセットアップ
React VITE のインストール
npm crate vite@latest
ページ遷移で必要になるのでインストール
npm i react-router-dom
今回は sass を使用しています
npm install -D sass
これはオプションです。なくても問題ありません
npm react icons
各ファイルのコード
React のファイル
App.tsx
import { BrowserRouter, Routes } from "react-router-dom";
// components
import Header from "./components/Header";
// sass
import "./sass/main.scss";
function App() {
return (
<>
<BrowserRouter>
<Header />
<Routes>
{/* ここにコンポーネントがないと下記の warning が出る */}
{/* No routes matched location "/" */}
{/* ここに必要なコンポーネントを記載の例: */}
{/* <Route path="/" element={<Home />} /> */}
</Routes>
</BrowserRouter>
</>
);
}
export default App;
components / Header.tsx のコード
import { useState, useEffect } from "react";
import { useLocation } from "react-router-dom";
import { Link } from "react-router-dom"; // react-router-dom
import { AiFillGithub } from "react-icons/ai"; // react-icons
// go to top
function ChangePageTop() {
window.scroll(0, 0); // ページの一番上に移動
}
function Header() {
const location = useLocation();
// console.log(location.pathname); // ex) /about
// hamburger menu
const [isMenuOpen, setIsMenuOpen] = useState(false);
// Headerの透明化
const [isTransparent, setIsTransparent] = useState(false);
// hamburger menu 開閉の管理
const toggleHam = () => {
setIsMenuOpen(!isMenuOpen);
};
// スクロールでHeaderの透明化
useEffect(() => {
function handleScroll() {
if (window.scrollY > 0) {
setIsTransparent(true);
} else {
setIsTransparent(false);
}
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return (
<>
<header className={isTransparent ? "header scrolled" : "header"}>
<span
onClick={toggleHam}
className={isMenuOpen ? "header unShownLayer" : "header"}
></span>
<nav className="nav">
{/* LOGO */}
<h1 className="navLogo">
<Link to="/" onClick={ChangePageTop}>
<div
className="commonTitle"
style={{ padding: 0, fontSize: "2rem" }}
>
KZ-DEV
</div>
</Link>
</h1>
<div className="navLinks">
<Link to="/" onClick={ChangePageTop}>
DOCS
</Link>
<Link to="/about" onClick={ChangePageTop}>
ABOUT
</Link>
<Link to="/contact" onClick={ChangePageTop}>
CONTACT
</Link>
{/* about & contact ページでは非表示 */}
<Link to="/test" onClick={ChangePageTop}>
{location.pathname !== "/about" && location.pathname !== "/contact" && (
<>
TEST
</>
)}
</Link>
</div>
{/* React Icons 使用 */}
<div className="navSNS">
<a
target="blank"
href="https://github.com/TechnoEmpire"
className="navSNS__link"
>
<AiFillGithub style={{ color: "white", fontSize: "2rem" }} />
</a>
</div>
{/* ======================= */}
{/* <!-- Hamburger Icon --> */}
{/* ======================= */}
<div className="headerHam">
<button
onClick={toggleHam}
className={
isMenuOpen ? "hamOpen headerHam__icon" : "headerHam__icon"
}
>
<span
className={
isMenuOpen
? "headerHam__icon--bar top iconWhite"
: "headerHam__icon--bar top"
}
></span>
<span className="headerHam__icon--bar middle"></span>
<span
className={
isMenuOpen
? "headerHam__icon--bar bottom iconWhite"
: "headerHam__icon--bar bottom"
}
></span>
</button>
</div>
{/* ======================= */}
{/* Hamburger Hidden Menu */}
{/* ======================= */}
<div
className={
isMenuOpen ? "hamOpen overWrap active hiddenMenu" : "overWrap"
}
>
<ul className="overWrap__box">
<li className="overWrap__box--item">
<Link
to="/"
onClick={() => {
ChangePageTop();
toggleHam();
}}
className="overWrap__box--link"
>
DOCUMENTATION
</Link>
</li>
<li className="overWrap__box--item">
<Link
to="/contact"
onClick={() => {
ChangePageTop();
toggleHam();
}}
className="overWrap__box--link"
>
CONTACT
</Link>
</li>
<li className="overWrap__box--item">
<Link
to="/about"
onClick={() => {
ChangePageTop();
toggleHam();
}}
className="overWrap__box--link"
>
ABOUT
</Link>
</li>
{/* about & contact ページでは非表示 */}
{location.pathname !== "/about" && location.pathname !== "/contact" && (
<>
<li className="overWrap__box--item">
<Link
to="/test"
onClick={() => {
ChangePageTop();
toggleHam();
}}
className="overWrap__box--link"
>
TEST
</Link>
</li>
</>
)}
</ul>
{/* ================================== */}
{/* React Icons */}
{/* ================================== */}
<div className="InHamburger">
<a href="https://github.com/TechnoEmpire" className="sns icon">
<AiFillGithub style={{ color: "white", fontSize: "2rem" }} />
</a>
</div>
</div>
</nav>
</header>
</>
);
}
export default Header;
Sass のファイル
main.scss
@use "./Header" as *;
@use "./Hamburger" as *;
@use "./CommonParts" as *;
_Setting.scss
/* Mixin---------------------------------------------- */
$breakpoints: (
's': 'screen and (max-width:480px)',
'm': 'screen and (max-width: 767px) and (min-width:480px)',
'l': 'screen and (max-width: 1000px) and (min-width: 767px)',
'xl': 'screen and (min-width: 1001px) ',
) !default;
@mixin mq($breakpoint: md) {
@media #{map-get($breakpoints, $breakpoint)} {
@content;
}
}
/* Common---------------------------------------------- */
html {
scroll-behavior: smooth;
}
body {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
-moz-osx-font-smoothing: grayscale;
overflow: hidden auto;
color: white;
background: #091c35;
font-family: 'Open Sans',sans-serif;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
}
_Heading.scss
@use "./setting" as *;
.header {
z-index: 2;
position: fixed;
top: 0;
left: 0;
width: 100%;
background-color: rgb(23, 23, 23);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
&.scrolled {
opacity: 0.9;
}
.nav {
display: grid;
grid-template-columns: 0.6fr 0.8fr 0.6fr;
place-items: center;
@include mq(s) {
grid-template-columns: 1fr 1fr ;
}
@include mq(m) {
grid-template-columns: 1fr 1fr ;
column-gap: 10%;
}
@include mq(l) {
grid-template-columns: 1fr 1fr ;
column-gap: 40%;
}
}
.navLinks {
@include mq(l) {
display: none;
}
@include mq(m) {
display: none;
}
@include mq(s) {
display: none;
}
a {
text-decoration: none;
color: rgb(221, 221, 221);
font-weight: 600;
font-size: 1.3rem;
margin: 0 12px;
display: inline-block;
}
}
.navLogo {
margin-top: 12px;
a {
text-decoration: none;
color: black;
font-size: 2rem;
margin: 0 12px;
display: inline-block;
@include mq(s) {
font-size: 1.6rem;
margin-top: 4px;
}
@include mq(m) {
font-size: 2rem;
margin-top: 6px;
}
@include mq(l) {
margin-top: 6px;
}
}
}
.navSNS {
@include mq(l) {
display: none;
}
@include mq(m) {
display: none;
}
@include mq(s) {
display: none;
}
a {
img {
width: 30px;
height: 30px;
}
}
}
}
Details
_Hamburger.scss
@use "./setting" as *;
// 画面全体を覆う膜
// JS (useState)
.unShownLayer {
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
cursor: pointer;
display: block;
transition: 0.6 all ease;
}
// ================================
// when a hamburger menu is open
// JS (useState)
// ================================
.hamOpen {
.headerHam__icon--bar.top {
transform: translateY(8px) rotate(45deg);
}
.headerHam__icon--bar.middle {
display: none;
}
.headerHam__icon--bar.bottom {
transform: translateY(-5px) rotate(132deg);
}
}
// ================================
// when a hamburger menu is closed
// ================================
.headerHam {
cursor: pointer;
display: none;
align-items: center;
z-index: 11;
transform: translate(0, -8px);
@include mq(s) {
display: block;
}
@include mq(m) {
display: block;
}
@include mq(l) {
display: block;
}
&__icon {
cursor: pointer;
background-color: none;
background: none;
border: none;
appearance: none;
outline: none;
padding: 0;
cursor: pointer;
width: 30px;
height: 13px;
position: relative;
margin-right: 6px;
&--bar {
background: rgb(223, 223, 223);
width: inherit;
height: 2px;
transition: transform 0.5s, opacity 0.5s;
display: block;
// JS (useState)
&.iconWhite {
background: white;
}
&.top {
position: absolute;
top: 0;
left: 0;
}
&.middle {
position: absolute;
top: 7px;
left: 0;
}
&.bottom {
position: absolute;
top: 14px;
left: 0;
}
}
}
}
// =====================
// JS (useState)
// =====================
.mask {
opacity: 0.4;
background-color: rgba(0, 0, 0, 0.8);
}
.overWrap {
position: fixed;
top: 0;
right: -20%;
height: 100vh;
opacity: 0;
pointer-events: none; // 押していない時は無効に
// transition: 0.2s all ease; // 開閉時のアニメーション
&__box {
color: white;
margin-top: 100px;
@include mq(s) {
margin-top: 60px;
margin-left: 20px;
}
@include mq(m) {
margin-left: 40px;
}
&--item {
margin: 36px 0;
list-style: none;
}
&--link {
color: white;
text-decoration: none;
font-size: 1.25rem;
letter-spacing: 1px;
list-style: none;
@include mq(s) {
font-size: 1rem;
}
}
}
}
// js
.overWrap.active {
pointer-events: auto !important; // burgerメニュー内を押せるようにする
z-index: 10;
}
// Hidden Menu
.hiddenMenu {
overflow-y: scroll;
position: fixed;
top: 0;
display: block;
opacity: 1;
z-index: 1;
height: 180vw;
background-color: rgba(0, 0, 0, 0.8);
width: 70vw;
height: 100vh;
@include mq(s) {
width: 92vw;
padding: 20px 0;
}
@include mq(m) {
width: 85vw;
}
}
.InHamburger {
margin-top: 48px;
text-align: center;
@include mq(s) {
margin-left: -4rem;
}
@include mq(m) {
margin-left: -10rem;
}
@include mq(l) {
margin-left: -10rem;
}
}
//! SP ONLY ACCORDION
.menuSp {
display: none;
@include mq(s) {
display: block;
}
@include mq(m) {
display: block;
}
@include mq(l) {
display: block;
}
}
終わり
今回は React でハンバーガーメニューを実装しました。
上記のコード実装の負担が減れば幸いです。
人気記事
オーストリア
メルボルン移住(ワーホリ)を検討している方へ
オーストリア
メルボルンで絶対に行きたい場所10選
英語
『ネイティブ監修』 オーストラリア厳選スラング30
オーストリア
海外のWEBエンジニア特化スクールの費用や授業内容や費用など完全解説(オーストラリア・メルボルン)
Kazu
オーストラリア在住歴7年。現地で英語学習後に日本語教師 → 工場就職 → 海外WEB開発ディプロマを取得→フリーランス。現在は余った時間をプログラミング&ブログに費やして生きています。
276