見出し画像

JavaScript攻略 ハンバーガーメニュー

ハンバーガーメニューだけでもコードはこんなにたくさんある。


<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <link rel="stylesheet" href="style.css" />
    <script src="script.js" defer></script>
    <title>第3週まとめ</title>
  </head>
  <body>
    <header>
      <button class="menu-button js-menu-button" id="menuButton">
        <img
          src="images/26971246.png"
          alt="メニュー"
          class="hamburger-menu js-hamburger-menu"
        />
      </button>

      <div class="menu-modal js-menu-modal" id="menuModal">
        <button class="close-button js-close-button" id="closeButton">x</button>
        <nav class="modal-nav-menu js-modal-nav-nemu" id="navMenu">
          <ul>
            <li><a href="#">Products</a></li>
            <li><a href="#">About Us</a></li>
            <li><a href="#">Dealer Locator</a></li>
          </ul>
        </nav>
      </div>
    </header>
  </body>
</html>

css
#menuButton {
  position: fixed;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  background: transparent;
  z-index: 1001;
  cursor: pointer;
}
/* メニューが開いたら、ハンバーガーPNGを非表示にする */
#menuButton.is-hidden {
  opacity: 0;
  pointer-events: none;
}
.hamburger-menu {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

/* 全画面オーバーレイ */
.menu-modal {
  position: fixed;
  inset: 0;
  background: rgba(10, 10, 10, 0.94);
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 0.8s ease,
    visibility 0.8s ease;
  z-index: 1000;
}
/* 開いた状態 */
.menu-modal.is-open {
  opacity: 1;
  visibility: visible;
}
/* 閉じるボタン */
.close-button {
  position: fixed;
  top: 24px;
  right: 24px;
  border: none;
  background: transparent;
  color: #fff;
  font-size: 32px;
  line-height: 1;
  cursor: pointer;
  z-index: 1005;
}
/* メニューの中央配置 */
.modal-nav-menu {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* リストの初期化 */
.modal-nav-menu ul {
  list-style: none;
  padding: 0;
  margin: 0;
  text-align: center;
}
/* リスト項目:最初は下にずらして透明 */
.modal-nav-menu li {
  opacity: 0;
  transform: translateY(30px);
  transition:
    opacity 0.8s ease,
    transform 0.8s ease;
  margin: 28px 0;
}
/* モーダルが開いたら、リストが上がりながら表示 */
.menu-modal.is-open .modal-nav-menu li {
  opacity: 1;
  transform: translateY(0);
}
/* 順番に出す */
.menu-modal.is-open .modal-nav-menu li:nth-child(1) {
  transition-delay: 0.1s;
}
.menu-modal.is-open .modal-nav-menu li:nth-child(2) {
  transition-delay: 0.2s;
}
.menu-modal.is-open .modal-nav-menu li:nth-child(3) {
  transition-delay: 0.3s;
}
/* リンク */
.modal-nav-menu a {
  color: white;
  text-decoration: none;
  font-size: 32px;
  letter-spacing: 0.08em;
  font-weight: 600;
}
/* ホバー時 */
.modal-nav-menu a:hover {
  opacity: 0.7;
}

JavaScript
// ハンバーガーメニュー
const menuButton = document.getElementById("menuButton");
const menuModal = document.getElementById("menuModal");

menuButton.addEventListener("click", function () {
  menuModal.classList.add("is-open");
  menuButton.classList.add("is-hidden");
  closeButton.addEventListener("click", function () {
    menuModal.classList.remove("is-open");
    menuButton.classList.remove("is-hidden");
  });
});

このCSSは大きく分けると、次の役割があります。

1. ハンバーガーボタンの見た目と位置
2. メニューが開いた時にハンバーガーを隠す
3. PNG画像のサイズ調整
4. 全画面オーバーレイ
5. 閉じるボタン
6. メニューリストの中央配置
7. リストをふわっと順番に表示
8. リンク文字のデザイン

では一行ずつ見ていきます。


ハンバーガーボタン

#menuButton {

id="menuButton" が付いた要素にCSSを当てます。

HTMLだと、たぶんこれです。

<button class="menu-button js-menu-button" id="menuButton">

つまり、ハンバーガーメニューのボタン本体です。


  position: fixed;

画面に固定します。

スクロールしても、常に同じ位置に表示されます。

ブラウザ画面の右上に固定するための指定

です。


  top: 20px;

画面の上から 20px の位置に配置します。


  right: 20px;

画面の右から 20px の位置に配置します。

つまり、

右上から少し内側に置く

ということです。


  width: 44px;

ボタンの横幅を 44px にします。


  height: 44px;

ボタンの高さを 44px にします。

これで、ボタンは

44px × 44px

の正方形になります。

スマホでも押しやすいサイズです。


  padding: 0;

ボタンの内側の余白を消します。

ボタンにはブラウザ標準で少し余白が入ることがあります。
それを消しています。


  border: none;

ボタンの枠線を消します。

これがないと、ボタンに灰色の枠が出ることがあります。


  background: transparent;

ボタンの背景を透明にします。

transparent は「透明」という意味です。


  z-index: 1001;

重なり順を指定しています。

数字が大きいほど前面に出ます。

ここでは、ハンバーガーボタンをかなり前面に表示しています。


  cursor: pointer;

マウスを乗せた時に、カーソルを指マークにします。

クリックできますよ

と分かりやすくするためです。


}

#menuButton のCSS指定はここまでです。


メニューが開いたら、ハンバーガーPNGを非表示にする

/* メニューが開いたら、ハンバーガーPNGを非表示にする */

これはコメントです。
CSSの動作には影響しません。

人間が見た時に、

ここは何のためのCSSか

分かりやすくするために書いています。


#menuButton.is-hidden {

これは、

id="menuButton" の要素に
is-hidden というクラスが付いた時

という意味です。

例えば通常はこうです。

<button id="menuButton">

JavaScriptで is-hidden が付くとこうなります。

<button id="menuButton" class="is-hidden">

実際にはclassが複数あるので、こういう感じです。

<button class="menu-button js-menu-button is-hidden" id="menuButton">

  opacity: 0;

透明にします。

見えなくなります。

ただし、要素自体はまだそこに存在しています。


  pointer-events: none;

クリックできなくします。

opacity: 0; だけだと、見えないのにクリックできてしまうことがあります。

それを防ぐために、

pointer-events: none;

を入れています。

つまり、

見えないし、押せない

という状態にしています。


}

#menuButton.is-hidden の指定はここまでです。


ハンバーガー画像

.hamburger-menu {

class="hamburger-menu" が付いた画像にCSSを当てます。

HTMLだとこれです。

<img src="images/26971246.png" alt="メニュー" class="hamburger-menu">

  width: 100%;

画像の横幅を、親要素いっぱいにします。

親要素は #menuButton です。

#menuButton は 44px なので、画像の横幅も 44px になります。


  height: 100%;

画像の高さを、親要素いっぱいにします。

つまり、画像も

44px × 44px

の中に収まります。


  object-fit: contain;

画像の縦横比を保ったまま、枠の中に収めます。

画像が横長・縦長でも、無理に引き伸ばさずに表示します。

画像をつぶさず、きれいに収める

指定です。


  display: block;

画像をブロック要素として扱います。

img は標準だと少し下に余白が出ることがあります。
display: block; にすると、その余白を防ぎやすいです。
※最下部に詳しい説明


}

.hamburger-menu の指定はここまでです。


全画面オーバーレイ

/* 全画面オーバーレイ */

コメントです。

ここから、画面全体を覆うメニュー部分のCSSです。


.menu-modal {

class="menu-modal" が付いた要素にCSSを当てます。

HTMLだとこれです。

<div class="menu-modal js-menu-modal" id="menuModal">

これはメニュー全体を包んでいる箱です。


  position: fixed;

画面に固定します。

スクロールしても、画面全体にかぶさるようにします。


  inset: 0;

これは、

top: 0;
right: 0;
bottom: 0;
left: 0;

と同じ意味です。

つまり、画面全体に広げます。

上も右も下も左も0
↓
画面いっぱい

です。


  background: rgba(10, 10, 10, 0.94);

背景色を指定しています。

rgba は、

red
green
blue
alpha

の略です。

rgba(10, 10, 10, 0.94)

は、かなり黒に近い色です。

最後の 0.94 は透明度です。

1.0 → 完全に不透明
0.0 → 完全に透明
0.94 → ほぼ黒、少しだけ透ける

です。


  opacity: 0;

最初は透明にします。

つまり、メニューは見えません。


  visibility: hidden;

見えない状態にします。

opacity: 0; だけだと透明なだけで、要素自体は存在しています。
visibility: hidden; を入れることで、見えない状態として扱います。


  transition:

ここから、変化をなめらかにする設定を書きます。


    opacity 0.8s ease,

opacity が変わる時に、0.8秒 かけて変化させます。

つまり、

透明 → 表示

がふわっとなります。

ease は、動きに少し緩急をつける指定です。


    visibility 0.8s ease;

visibility の変化にも 0.8秒 を指定しています。

ただし、実際にふわっと見える主役は opacity です。
visibility は、見える・見えないの状態管理のために使っています。


  z-index: 1000;

重なり順です。

メニューオーバーレイを前面に出します。

ただし、ハンバーガーボタンは 1001、閉じるボタンは 1005 なので、それらの方がさらに前に出ます。


}

.menu-modal の指定はここまでです。


開いた状態

/* 開いた状態 */

コメントです。

ここから、メニューが開いた時のCSSです。


.menu-modal.is-open {

これは、

menu-modal に is-open が付いた時

という意味です。

通常はこうです。

<div class="menu-modal">

JavaScriptで is-open が付くとこうなります。

<div class="menu-modal is-open">

その時に、このCSSが効きます。


  opacity: 1;

透明度を 1 にします。

つまり、完全に見える状態にします。


  visibility: visible;

表示状態にします。


}

.menu-modal.is-open の指定はここまでです。

この部分の動きはこうです。

通常時
opacity: 0;
visibility: hidden;

開いた時
opacity: 1;
visibility: visible;

JavaScriptで is-open を付けることで、メニューがふわっと表示されます。


閉じるボタン

/* 閉じるボタン */

コメントです。


.close-button {

class="close-button" が付いた要素にCSSを当てます。

HTMLだとこれです。

<button class="close-button js-close-button" id="closeButton">×</button>

  position: fixed;

閉じるボタンを画面に固定します。


  top: 24px;

画面上から 24px の位置に置きます。


  right: 24px;

画面右から 24px の位置に置きます。

つまり、右上に置いています。


  border: none;

ボタンの枠線を消します。


  background: transparent;

ボタンの背景を透明にします。


  color: #fff;

文字色を白にします。

#fff は #ffffff の省略形で、白です。


  font-size: 32px;

文字サイズを 32px にします。

× のサイズです。


  line-height: 1;

行の高さを文字サイズとほぼ同じにします。

× が上下にズレにくくなります。


  cursor: pointer;

マウスを乗せると指マークにします。


  z-index: 1005;

閉じるボタンをかなり前面に出します。

menu-modal が 1000、menuButton が 1001 なので、close-button の 1005 はさらに上です。


}

.close-button の指定はここまでです。


メニューの中央配置

/* メニューの中央配置 */

コメントです。


.modal-nav-menu {

class="modal-nav-menu" が付いた nav にCSSを当てます。

HTMLだとこれです。

<nav class="modal-nav-menu js-modal-nav-menu" id="navMenu">

  height: 100%;

高さを親要素いっぱいにします。

親は .menu-modal です。
.menu-modal は全画面なので、nav も画面いっぱいの高さになります。


  display: flex;

Flexboxを使います。

Flexboxは、要素を中央に置いたり、横並びにしたりするのが得意です。


  justify-content: center;

横方向の中央に配置します。


  align-items: center;

縦方向の中央に配置します。

つまり、

display: flex;
justify-content: center;
align-items: center;

この3つで、リストを画面のど真ん中に置いています。


}

.modal-nav-menu の指定はここまでです。


リストの初期化

/* リストの初期化 */

コメントです。


.modal-nav-menu ul {

.modal-nav-menu の中にある ul にCSSを当てます。

HTMLだとこれです。

<ul>
  <li><a href="#">Products</a></li>
  <li><a href="#">About Us</a></li>
  <li><a href="#">Dealer Locator</a></li>
</ul>

  list-style: none;

リストの黒丸を消します。

普通の ul には、

・Products
・About Us
・Dealer Locator

のような黒丸が付きます。

それを消しています。


  padding: 0;

ul の内側の余白を消します。

ブラウザ標準で ul には左側に余白があります。
それをリセットしています。


  margin: 0;

ul の外側の余白を消します。


  text-align: center;

中の文字を中央揃えにします。


}

.modal-nav-menu ul の指定はここまでです。


リスト項目:最初は下にずらして透明

/* リスト項目:最初は下にずらして透明 */

コメントです。

ここから、Products などの各項目にアニメーションを設定します。


.modal-nav-menu li {

.modal-nav-menu の中にある li にCSSを当てます。

つまり、

Products
About Us
Dealer Locator

それぞれの項目です。


  opacity: 0;

最初は透明にします。


  transform: translateY(30px);

最初は下に 30px ずらします。

translateY は縦方向の移動です。

translateY(30px) → 下に30px
translateY(0)    → 元の位置

です。


  transition:

変化をなめらかにする設定です。


    opacity 0.8s ease,

透明度の変化を 0.8秒 かけて行います。


    transform 0.8s ease;

位置の変化を 0.8秒 かけて行います。

つまり、リスト項目は、

透明 → 表示
下に30px → 元の位置

を 0.8秒 かけて行います。


  margin: 28px 0;

上下に 28px の余白を付けます。

左右は 0 です。

上 28px
下 28px
左右 0

という意味です。

リスト同士の間隔を空けています。


}

.modal-nav-menu li の指定はここまでです。


モーダルが開いたら、リストが上がりながら表示

/* モーダルが開いたら、リストが上がりながら表示 */

コメントです。


.menu-modal.is-open .modal-nav-menu li {

これは少し長いですが、意味は、

menu-modal に is-open が付いている時、
その中の modal-nav-menu の中の li

です。

つまり、メニューが開いた時だけ、リスト項目にこのCSSを当てます。


  opacity: 1;

透明だったリストを見えるようにします。


  transform: translateY(0);

下にずれていたリストを元の位置に戻します。

これにより、

下からふわっと上がってくる

動きになります。


}

この指定はここまでです。


順番に出す

/* 順番に出す */

コメントです。

ここから、リストが一つずつ遅れて出る設定です。


.menu-modal.is-open .modal-nav-menu li:nth-child(1) {

メニューが開いた時の、1つ目の li にだけCSSを当てます。

1つ目はこれです。

<li><a href="#">Products</a></li>

  transition-delay: 0.1s;

アニメーション開始を 0.1秒 遅らせます。


}

1つ目の設定はここまでです。


.menu-modal.is-open .modal-nav-menu li:nth-child(2) {

2つ目の li にCSSを当てます。

2つ目はこれです。

<li><a href="#">About Us</a></li>

  transition-delay: 0.2s;

アニメーション開始を 0.2秒 遅らせます。

1つ目より少し遅れて出ます。


}

2つ目の設定はここまでです。


.menu-modal.is-open .modal-nav-menu li:nth-child(3) {

3つ目の li にCSSを当てます。

3つ目はこれです。

<li><a href="#">Dealer Locator</a></li>

  transition-delay: 0.3s;

アニメーション開始を 0.3秒 遅らせます。


}

3つ目の設定はここまでです。

この3つで、

Products        0.1秒後に表示
About Us        0.2秒後に表示
Dealer Locator  0.3秒後に表示

になります。

もっと「ふわっ、ふわっ、ふわっ」とゆっくり出したいなら、こうすると良いです。

.menu-modal.is-open .modal-nav-menu li:nth-child(1) {
  transition-delay: 0.25s;
}

.menu-modal.is-open .modal-nav-menu li:nth-child(2) {
  transition-delay: 0.45s;
}

.menu-modal.is-open .modal-nav-menu li:nth-child(3) {
  transition-delay: 0.65s;
}

リンク

/* リンク */

コメントです。


.modal-nav-menu a {

.modal-nav-menu の中にある a タグにCSSを当てます。

つまり、リンク文字です。

<a href="#">Products</a>

  color: white;

文字色を白にします。


  text-decoration: none;

リンクの下線を消します。

普通のリンクは青色で下線が付きます。
それを消しています。


  font-size: 32px;

文字サイズを 32px にします。

大きめで、全画面メニューらしい見た目になります。


  letter-spacing: 0.08em;

文字間を少し広げます。

letter-spacing は文字と文字の間隔です。

Products

を少しゆったり見せる効果があります。


  font-weight: 600;

文字を少し太くします。

400 が普通くらい、700 が太字くらいです。
600 は少し太めです。


}

リンクの指定はここまでです。


ホバー時

/* ホバー時 */

コメントです。

hover はマウスを乗せた時の状態です。


.modal-nav-menu a:hover {

.modal-nav-menu の中にあるリンクにマウスを乗せた時のCSSです。


  opacity: 0.7;

少し透明にします。

通常より薄くなるので、

今マウスが乗っていますよ

と分かります。


}

ホバー時の指定はここまでです。


全体の流れ

このCSS全体の動きはこうです。

最初
↓
ハンバーガーボタンだけ右上に表示
メニュー本体は opacity: 0 / visibility: hidden で非表示

ハンバーガーをクリック
↓
JavaScriptで .menu-modal に is-open を付ける
↓
.menu-modal.is-open が効く
↓
全画面オーバーレイがふわっと表示

さらに
↓
.menu-modal.is-open .modal-nav-menu li が効く
↓
リストが下から上がりながら表示

transition-delay により
↓
Products
About Us
Dealer Locator
の順番で表示

一番大事な関係はこれです。

.menu-modal {
  opacity: 0;
  visibility: hidden;
}

通常は非表示。

.menu-modal.is-open {
  opacity: 1;
  visibility: visible;
}

is-open が付いたら表示。

そしてJavaScriptで、

menuModal.classList.add("is-open");

または、

menuModal.classList.toggle("is-open");

をすることで、CSSの状態を切り替えています。

※css display

display: block; は テキストだけでなく画像にも使えます
というより、display は ほぼすべてのHTML要素に指定できるCSS です。

例えば画像にも使えます。

img {
  display: block;
}

まず display とは?

display は、その要素を 画面上でどういう箱として扱うか を決めるCSSです。

ざっくり言うと、

この要素を
・横に並ぶ部品として扱うのか
・1行まるごとの箱として扱うのか
・消すのか
・flexの箱にするのか

を決めます。


代表的な display

1. display: block;

.box {
  display: block;
}

これは、1行まるごと使う箱 になります。

代表例はこれです。

<div></div>
<p></p>
<section></section>
<h1></h1>

これらは最初からだいたい display: block; です。

イメージはこうです。

要素A ─────────────
要素B ─────────────
要素C ─────────────

特徴は、

・基本的に縦に並ぶ
・width / height が効く
・margin / padding が素直に効く
・親の幅いっぱいに広がりやすい

です。


2. display: inline;

span {
  display: inline;
}

これは、文章の中に入る部品 です。

代表例はこれです。

<span></span>
<a></a>
<strong></strong>

イメージはこうです。

今日は <span>暑い</span> ですね

横に流れていきます。

特徴は、

・文章の一部として横に並ぶ
・width / height が基本的に効きにくい
・上下のmarginが効きにくい
・改行されにくい

です。


3. display: inline-block;

.button {
  display: inline-block;
}

これは、横に並ぶけど、箱としてサイズ指定もできる 便利な指定です。

イメージは、

[ボタンA] [ボタンB] [ボタンC]

特徴は、

・横に並ぶ
・width / height が効く
・padding / margin が使いやすい

ボタン風リンクによく使います。

a {
  display: inline-block;
  padding: 12px 24px;
}

4. display: flex;

.menu {
  display: flex;
}

これは、中の要素を並べるための指定です。

例えば、

<nav class="menu">
  <a>Products</a>
  <a>About</a>
  <a>Contact</a>
</nav>
.menu {
  display: flex;
  justify-content: center;
  align-items: center;
}

こうすると、中のリンクを横並びにしたり、中央配置したりできます。


5. display: none;

.menu {
  display: none;
}

これは、完全に画面から消す 指定です。

特徴は、

・見えない
・場所も取らない
・クリックもできない
・アニメーションしにくい

です。

ハンバーガーメニューの初期状態でよく使いますが、ふわっと表示したい場合は opacity や visibility の方が向いています。


画像に display: block; を使えるの?

使えます。

画像 <img> は、初期状態ではだいたい inline系の要素 として扱われます。

<img src="sample.png" alt="" />

画像は見た目は四角い箱ですが、ブラウザ的には「文字と同じ行に並ぶ部品」に近い扱いです。

だから、画像の下に少し余白が出ることがあります。


なぜ画像の下に余白ができるの?

画像は初期状態では、文字と同じように ベースライン に揃えられます。

文字には、下に少し余白があります。

例えばアルファベットの、

g
p
y

みたいな文字は、下に伸びますよね。

ブラウザは画像も文字と同じ行に置けるようにしているので、その「下に伸びる文字用のスペース」が残ることがあります。

イメージです。

画像 inline の場合

┌───────┐
│  img  │
└───────┘
──────── ← 文字のベースライン
   ↑
ここに少し余白が出ることがある

なので、画像にこれを指定します。

img {
  display: block;
}

すると、画像は文章の一部ではなく、普通の箱として扱われます。

画像 block の場合

┌───────┐
│  img  │
└───────┘
余計なベースライン余白が出にくい

テキストでも同じ余白が出るの?

少し違います。

テキストそのものは、もともと文字なので、ベースラインや行の高さがあります。

例えば、

<p>Products</p>

には、標準でこういう余白があります。

pタグには標準の margin がある
文字には line-height がある

だからテキストの場合に余白が出る理由は、主にこの2つです。

1. margin
2. line-height

画像の下の謎の余白は、主に inline要素としてのベースラインの影響です。


テキストの余白を消したい場合

例えば p の余白を消すなら、

p {
  margin: 0;
}

文字の行間を調整したいなら、

p {
  line-height: 1.5;
}

または詰めたいなら、

p {
  line-height: 1;
}

です。


画像の余白を消す方法は他にもある

画像下の余白を消す方法は、display: block; 以外にもあります。

方法1:display block

一番よく使います。

img {
  display: block;
}

方法2:vertical-align

画像をinlineのまま扱いたい場合はこれもあります。

img {
  vertical-align: bottom;
}

または、

img {
  vertical-align: middle;
}

ただ、単独画像やアイコン画像なら display: block; が分かりやすいです。


今回のハンバーガー画像ではなぜ使う?

今回のHTMLはこうですね。

<button id="menuButton">
  <img src="images/26971246.png" alt="メニュー" class="hamburger-menu" />
</button>

CSSはこうです。

#menuButton {
  width: 44px;
  height: 44px;
  padding: 0;
}

.hamburger-menu {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}

ここで display: block; を入れる理由は、

画像をボタンの中で余計なズレなく扱うため

です。

img が inline のままだと、ボタンの中で微妙に下に余白ができたり、中央からズレたりすることがあります。


かなり大事な整理

display: block

箱として扱う
縦に並ぶ
width / height が効く

display: inline

文章の一部として扱う
横に並ぶ
width / height が効きにくい

display: inline-block

横に並ぶ
でも箱としてサイズ指定できる

display: flex

中の要素を並べたり中央配置したりする

display: none

完全に消す
場所も取らない

今回の一番のポイントはこれです。

.hamburger-menu {
  display: block;
}

これは、

画像を文字の一部ではなく、普通の箱として扱う
↓
画像下の変な余白やズレを防ぐ

という目的です。

なので、画像にも display: block; は普通に使います。
むしろ、画像をレイアウト部品として使う時にはかなりよく使います。

いいなと思ったら応援しよう!