ハンバーガーメニューの実装3種(JavaScript、jQuery、CSSオンリー)

今回はハンバーガーメニューです!
アコーディオンパネル同様、こちらも条件付きでCSSオンリーでの実装ができそうなので試してみました。
ネイティブのJavaScript、jQueryでの実装方法もあわせて備忘録として残します。

ちなみにアコーディオンパネルの記事はこちらです。

目次

JavaScript ハンバーガーメニュー

ネイティブのJavaScriptで動かすハンバーガーメニューです。
下記の流れで動作します。

  • 開閉ボタン<div class=”header__menu-btn” id=”spMenuBtn”>をクリック。
  • 開閉ボタンをラップしている要素、<div class=”header__inner” id=”headerInner”>にactiveというclassが付与される。
  • <div class=”header__inner actve” id=”headerInner”>の配下にある<div class=”header__menu”> と 、
    <div class=”header__menu-btn” id=”spMenuBtn”>の中の<span>に当たるCSSが変化する。
    (ボタンがハンバーガーからバッテンに、非表示だったメニューが表示される。)

動作サンプル

See the Pen Humberger VanillaJS by Yoshihiro Hotta (@yoshihiro-hotta) on CodePen.

HTML

<div class="container">
    <header class="header">
        <div class="header__inner" id="headerInner">
            <a class="header__logo" href="">
                <img src="https://zenweb.info/wp-content/uploads/2023/05/hamburger-dummy-logo-blue.svg" alt="dummy logo">
            </a>
            <div class="header__menu">
                <ul class="menu__list">
                    <li class="menu__item">
                    <a class="menu__link" href="">会社概要</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">お知らせ</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">採用情報</a>
                    </li>
                </ul>
            </div>
            <div class="header__menu-btn" id="spMenuBtn">
                <span></span>
                <span></span>
                <span></span>
                <span>MENU</span>
            </div>
        </div>
    </header>
</div>

CSS

/* すべての画面幅で適用されるCSS  */
.container {
    width: 100%;
}
.header {
    width: 100%;
    box-shadow: 0px 4px 4px -1px rgba(0,0,0,0.23);
}
.header__inner {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.header__logo {
    display: block;
    font-size: 0;
}
.header__logo img {
    width: 100%;
    height: auto;
}

/* パソコンサイズ用のCSS  */
@media screen and (min-width:768px) {
    .header__inner {
        padding: 20px;
    }
    .header__logo {
        width: 160px;
        height: 19px;
        transition: opacity .2s ease;
    }
    .header__logo:hover {
        opacity: .7;
    }
    .menu__list {
        display: flex;
    }
    .menu__item:nth-child(n+2) {
        margin-left: 20px;
    }
    .menu__link {
        position: relative;
        display: block;
    }
    .menu__link::after {
        content: '';
        position: absolute;
        bottom: -5px;
        left: 0;
        width: 100%;
        height: 2px;
        background-color: #019ac6;
        transform: scaleX(0);
        transform-origin: left center;
        transition: transform .2s ease;
    }
    .menu__link:hover::after {
        transform: scaleX(1);
    }
    .header__menu-btn {
        display: none;
    }
}

/* スマートフォン、タブレットサイズ用のCSS */
@media screen and (max-width:767px) {
    .container {
        overflow: hidden;
    }
    .header {
        position: fixed;

        background: #fff;
    }
    .header__inner {
        position: relative;
        z-index: 10;
    }
    .header__logo {
        width: 125px;
        height: 15px;
        margin-left: 10px;
    }
    .header__menu {
        position: fixed;
        z-index: 5;
        top: 50px;
        width: 100%;
        height: calc(100vh - 50px);
        background: #fff;
        opacity: 0;
        visibility: hidden;
        transition: opacity .2s ease;
    }
    .header__inner.active .header__menu {
        opacity: 1;
        visibility: visible
    }
    .menu__list {
        border-top: 1px solid #d7d7d7;
    }
    .menu__item {
        border-bottom: 1px solid #d7d7d7;
    }
    .menu__link {
        display: block;
        padding: 13px 10px;
        color: #019ac6;
    }
    .header__menu-btn {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: end;
        width: 50px;
        height: 50px;
        padding: 5px;
        cursor: pointer;
    }
    .header__menu-btn span:nth-child(-n+3) {
        position: absolute;
        display: block;
        width: 30px;
        height: 2px;
        background: #019ac6;
    }
    .header__menu-btn span:nth-child(1) {
        top: 10px;
        transition: all .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(1) {
        top: 20px;
        transform: rotate(45deg);
    }
    .header__menu-btn span:nth-child(2) {
        top: 18px;
        transition: opacity .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(2) {
        opacity: 0;
    }
    .header__menu-btn span:nth-child(3) {
        top: 26px;
        transition: all .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(3) {
        top: 20px;
        transform: rotate(-45deg);
    }
    .header__menu-btn span:nth-child(4) {
        font-size: 10px;
        color: #019ac6;
    }
}

JavaScript

// スマホ・タブレットサイズ時のみ表示されるメニューの開閉ボタンを変数に格納。
const spMenuBtn = document.getElementById("spMenuBtn");

// メニューや開閉ボタンをラップしている要素を変数に格納。
const headerInner = document.getElementById("headerInner");

// 開閉ボタンをクリックすると発火。
spMenuBtn.addEventListener("click", () => {
  // ラップ要素にactiveというクラスを付与する。
  headerInner.classList.toggle("active");
});

jQuery ハンバーガーメニュー

jQueryで動かすハンバーガーメニューです。

やっていること自体はJavaScriptと同じです!
DOMの指定の仕方がネイティブのJavaScriptと比べて楽ですね。

動作サンプル

See the Pen Hamburger jQuery by Yoshihiro Hotta (@yoshihiro-hotta) on CodePen.

HTML

<div class="container">
    <header class="header">
        <div class="header__inner" id="headerInner">
            <a class="header__logo" href="">
                <img src="https://zenweb.info/wp-content/uploads/2023/05/hamburger-dummy-logo-orange.svg" alt="dummy logo">
            </a>
            <div class="header__menu">
                <ul class="menu__list">
                    <li class="menu__item">
                    <a class="menu__link" href="">会社概要</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">お知らせ</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">採用情報</a>
                    </li>
                </ul>
            </div>
            <div class="header__menu-btn" id="spMenuBtn">
                <span></span>
                <span></span>
                <span></span>
                <span>MENU</span>
            </div>
        </div>
    </header>
</div>

CSS

/* すべての画面幅で適用されるCSS  */
.container {
    width: 100%;
}
.header {
    width: 100%;
    box-shadow: 0px 4px 4px -1px rgba(0,0,0,0.23);
}
.header__inner {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.header__logo {
    display: block;
    font-size: 0;
}
.header__logo img {
    width: 100%;
    height: auto;
}

/* パソコンサイズ用のCSS  */
@media screen and (min-width:768px) {
    .header__inner {
        padding: 20px;
    }
    .header__logo {
        width: 160px;
        height: 19px;
        transition: opacity .2s ease;
    }
    .header__logo:hover {
        opacity: .7;
    }
    .menu__list {
        display: flex;
    }
    .menu__item:nth-child(n+2) {
        margin-left: 20px;
    }
    .menu__link {
        position: relative;
        display: block;
    }
    .menu__link::after {
        content: '';
        position: absolute;
        bottom: -5px;
        left: 0;
        width: 100%;
        height: 2px;
        background-color: #F5675B;
        transform: scaleX(0);
        transform-origin: left center;
        transition: transform .2s ease;
    }
    .menu__link:hover::after {
        transform: scaleX(1);
    }
    .header__menu-btn {
        display: none;
    }
}

/* スマートフォン、タブレットサイズ用のCSS */
@media screen and (max-width:767px) {
    .container {
        overflow: hidden;
    }
    .header {
        position: fixed;
        background: #fff;
    }
    .header__inner {
        position: relative;
        z-index: 10;
    }
    .header__logo {
        width: 125px;
        height: 15px;
        margin-left: 10px;
    }
    .header__menu {
        position: fixed;
        z-index: 5;
        top: 50px;
        width: 100%;
        height: calc(100vh - 50px);
        background: #fff;
        opacity: 0;
        visibility: hidden;
        transition: opacity .2s ease;
    }
    .header__inner.active .header__menu {
        opacity: 1;
        visibility: visible
    }
    .menu__list {
        border-top: 1px solid #d7d7d7;
    }
    .menu__item {
        border-bottom: 1px solid #d7d7d7;
    }
    .menu__link {
        display: block;
        padding: 13px 10px;
        color: #F5675B;
    }
    .header__menu-btn {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: end;
        width: 50px;
        height: 50px;
        padding: 5px;
        cursor: pointer;
    }
    .header__menu-btn span:nth-child(-n+3) {
        position: absolute;
        display: block;
        width: 30px;
        height: 2px;
        background: #F5675B;
    }
    .header__menu-btn span:nth-child(1) {
        top: 10px;
        transition: all .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(1) {
        top: 20px;
        transform: rotate(45deg);
    }
    .header__menu-btn span:nth-child(2) {
        top: 18px;
        transition: opacity .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(2) {
        opacity: 0;
    }
    .header__menu-btn span:nth-child(3) {
        top: 26px;
        transition: all .2s ease;
    }
    .header__inner.active .header__menu-btn span:nth-child(3) {
        top: 20px;
        transform: rotate(-45deg);
    }
    .header__menu-btn span:nth-child(4) {
        font-size: 10px;
        color: #F5675B;
    }
}

jQuery

// スマホ・タブレットサイズ時のみ表示されるメニューの開閉ボタンを変数に格納。
const spMenuBtn = $("#spMenuBtn");

// メニューや開閉ボタンをラップしている要素を変数に格納。
const headerInner = $("#headerInner");

// 開閉ボタンをクリックすると発火。
spMenuBtn.click(function() {
  // ラップ要素にactiveというクラスを付与する。
  headerInner.toggleClass("active");
});

CSSオンリー ハンバーガーメニュー

HTMLとCSSのみで動かすハンバーガーメニューです。
開閉ボタン部分をinputタグ(checkbox)とlabelタグにすることで、jQueryやJavaScriptを使わず実装することができます。

ハンバーガーメニューの時と同様、チェックボックスにチェックが入っているかどうかを見て、見た目を変えたい要素に当たるCSSを変更します。

チェックボックスの後の要素に当たるスタイルを変えたい場合は隣接兄弟結合子を使いましたが、今回はチェックボックスの前の要素のスタイルも変えたかったので、擬似クラス:has() を使って要素の指定を行いました。

MDN Web Docs
:has() - CSS: カスケーディングスタイルシート | MDN :has() は CSS の疑似クラス関数で、引数として渡される相対セレクターのいずれかが、その要素から辿ってアンカーとして少なくとも一つの要素とマッチする場合に、その要素...

問題点として、上記のMDNの記事にもある通り:has() は2023年6月1日現在 FireFoxで「User must explicitly enable this feature.(ユーザーはこの機能を明示的に有効にする必要があります。)」となっています。

どうやら、FireFoxで:has() を動作させるためには、ユーザー側でFireFoxブラウザの設定を変更する必要があるようです。
案件などで用いられる際はこの点にご注意ください。

動作サンプル

動作の流れは以下のようになります。

  • 開閉ボタンのラベル<label class=”header__menu-label” for=”spMenuBtn”>をクリック。
  • 連動するチェックボックス<input type=”checkbox” class=”header__menu-input” id=”spMenuBtn”>が、チェックが入った状態になる。
  • <input type=”checkbox” class=”header__menu-input” id=”spMenuBtn”>の直前にある<div class=”header__menu”> と 、
    直後にある<label class=”header__menu-label” for=”spMenuBtn”>の中の<span>に当たるCSSが変化する。
    (ボタンがハンバーガーからバッテンに、非表示だったメニューが表示される。)

See the Pen Untitled by Yoshihiro Hotta (@yoshihiro-hotta) on CodePen.

HTML

<div class="container">
    <header class="header">
        <div class="header__inner" id="headerInner">
            <a class="header__logo" href="">
                <img src="https://zenweb.info/wp-content/uploads/2023/05/hamburger-dummy-logo-green.svg" alt="dummy logo">
            </a>
            <div class="header__menu">
                <ul class="menu__list">
                    <li class="menu__item">
                    <a class="menu__link" href="">会社概要</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">お知らせ</a>
                    </li>
                    <li class="menu__item">
                    <a class="menu__link" href="">採用情報</a>
                    </li>
                </ul>
            </div>
            <input type="checkbox" class="header__menu-input" id="spMenuBtn">
            <label class="header__menu-label" for="spMenuBtn">
                <span></span>
                <span></span>
                <span></span>
                <span>MENU</span>
            </label>
        </div>
    </header>
</div>

CSS

/* すべての画面幅で適用されるCSS  */
.container {
    width: 100%;
}
.header {
    width: 100%;
    box-shadow: 0px 4px 4px -1px rgba(0,0,0,0.23);
}
.header__inner {
    width: 100%;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.header__logo {
    display: block;
    font-size: 0;
}
.header__logo img {
    width: 100%;
    height: auto;
}
.header__menu-input {
    display: none;
}

/* パソコンサイズ用のCSS  */
@media screen and (min-width:768px) {
    .header__inner {
        padding: 20px;
    }
    .header__logo {
        width: 160px;
        height: 19px;
        transition: opacity .2s ease;
    }
    .header__logo:hover {
        opacity: .7;
    }
    .menu__list {
        display: flex;
    }
    .menu__item:nth-child(n+2) {
        margin-left: 20px;
    }
    .menu__link {
        position: relative;
        display: block;
    }
    .menu__link::after {
        content: '';
        position: absolute;
        bottom: -5px;
        left: 0;
        width: 100%;
        height: 2px;
        background-color: #12DE40;
        transform: scaleX(0);
        transform-origin: left center;
        transition: transform .2s ease;
    }
    .menu__link:hover::after {
        transform: scaleX(1);
    }
    .header__menu-label {
        display: none;
    }
}

/* スマートフォン、タブレットサイズ用のCSS*/
@media screen and (max-width:767px) {
    .container {
        overflow: hidden;
    }
    .header {
        position: fixed;
        background: #fff;
    }
    .header__inner {
        position: relative;
        z-index: 10;
    }
    .header__logo {
        width: 125px;
        height: 15px;
        margin-left: 10px;
    }
    .header__menu {
        position: fixed;
        z-index: 5;
        top: 50px;
        width: 100%;
        height: calc(100vh - 50px);
        background: #fff;
        opacity: 0;
        visibility: hidden;
        transition: opacity .2s ease;
    }
    /*
    疑似クラス:hasを使うことで、
    直前に「.header__menu-input:checked」がある
    「.header__menu」に適用される。
    */
    .header__menu:has(+ .header__menu-input:checked ) {
        opacity: 1;
        visibility: visible
    }
    .menu__list {
        border-top: 1px solid #d7d7d7;
    }
    .menu__item {
        border-bottom: 1px solid #d7d7d7;
    }
    .menu__link {
        display: block;
        padding: 13px 10px;
        color: #12DE40;
    }
    .header__menu-label {
        position: relative;
        display: flex;
        justify-content: center;
        align-items: end;
        width: 50px;
        height: 50px;
        padding: 5px;
        cursor: pointer;
    }
    .header__menu-label span:nth-child(-n+3) {
        position: absolute;
        display: block;
        width: 30px;
        height: 2px;
        background: #12DE40;
    }
    .header__menu-label span:nth-child(1) {
        top: 10px;
        transition: all .2s ease;
    }
    .header__menu-input:checked + .header__menu-label span:nth-child(1) {
        top: 20px;
        transform: rotate(45deg);
    }
    .header__menu-label span:nth-child(2) {
        top: 18px;
        transition: opacity .2s ease;
    }
    .header__menu-input:checked + .header__menu-label span:nth-child(2) {
        opacity: 0;
    }
    .header__menu-label span:nth-child(3) {
        top: 26px;
        transition: all .2s ease;
    }
    .header__menu-input:checked + .header__menu-label span:nth-child(3) {
        top: 20px;
        transform: rotate(-45deg);
    }
    .header__menu-label span:nth-child(4) {
        font-size: 10px;
        color: #12DE40;
    }
}

最後に

ハンバーガーメニューも、CSSオンリーでほぼ同じ動作を実現することができました!

HTML、CSSは気付いたら進化してるので要チェックですね。
今後も役立ちそうな物があればブログで紹介していきます。

目次