ナビゲーションメニューを作ってみよう!プルダウンやトグルメニューの実装方法について解説!

チュートリアル

はじめに

ウェブサイトを制作するにあたって、ナビゲーションメニューはとても重要な要素です。ナビゲーションメニューはユーザーエクスペリエンスやアクセシビリティの向上に大きく貢献し、設計が疎かではユーザーやウェブサイト運営者の期待に応えられるような効果を生み出すことはできません。

この記事ではウェブ制作初心者の方向けに、レスポンシブに対応したナビゲーションメニューの作成を通じて、ウェブ制作やHTML/CSSの基本的なところを解説します。

この記事の対象者

  • ウェブ制作初心者の方
  • ナビゲーションメニューに関心のある方
  • モバイルファーストなデザインを意識している方

この記事の目標

  • ナビゲーションメニューを作れる
  • プルダウンメニューを実装できる
  • レスポンシブデザインを実装できる
  • トグルメニューを実装できる

ナビゲーションメニューについて

ナビゲーションメニューはユーザーがウェブサイト訪れた際、コンテンツにアクセスするための窓口になります。

ウェブサイト内の異なるセクションやページへの案内役であり、ユーザーエクスペリエンスやアクセシビリティを向上させるために欠かせない要素です。

ここでは、ナビゲーションメニューの主な役割や効果、ユーザーへの影響について簡単に解説します。

ナビゲーションメニューの役割

ナビゲーションメニューはウェブサイト内の情報を整理し、ユーザーが求める情報へ素早くアクセスできるよう手助けをします。

例えば、ブログ記事、商品カテゴリー、連絡先情報など、ウェブサイトのさまざまなコンテンツへのリンクがメニューによって効果的にまとめられています。これにより、ユーザーは簡単に目的のコンテンツを見つけることができます。

カテゴリーとユーザーフロー

ナビゲーションメニューは、ウェブサイトのカテゴリー構造やユーザーフローを反映しています。

例えば、ECサイトでは商品カテゴリーやブランド別のセクションへのリンクがあり、ニュースサイトではカテゴリーごとの記事へのアクセスが可能です。

カテゴリーの適切な整理と順序付けによって、ユーザーは迷わずに目的の情報にたどり着けるようになります。

デザインと視覚的な誘導

ナビゲーションメニューのデザインは、ウェブサイトのブランドイメージやスタイルに合わせてカスタマイズされることが一般的です。

また、視覚的な要素(ホバーエフェクトやアイコン)を取り入れることで、ユーザーに選択可能な項目を明示的に示し、ビゲーションの利便性を高める役割も果たします。

ナビゲーションメニューの作り方

基本的なナビゲーションメニューの実装方法について解説します。また、動作確認に使用するブラウザは「Google Chrome」であることを前提とします。

ナビゲーションメニューはヘッダーに配置されることが多いので、この記事でもヘッダーに配置することを前提として解説します。初心者の方は実際に手を動かして実装することでより理解が深まるでしょう。構成は以下のとおりです。

  • ホーム
  • サービス
    • ウェブデザイン
    • モバイルアプリ
    • SEOコンサルティング
  • ポートフォリオ
  • お問い合わせ

トップレベルのメニューとして、「ホーム」、「サービス」、「ポートフォリオ」、「お問い合わせ」があり、「サービス」は更に「ウェブデザイン」、「モバイルアプリ」、「SEOコンサルティング」のカテゴリーに分類されています。

実装は以下の手順で行います。

  1. HTMLの作成
  2. 基本的なスタイリング
  3. 画面サイズでメニューの配置を切り替える
  4. トグルメニューの追加

それでは始めましょう。

HTMLの作成

デスクトップなど分かりやすいところにディレクトリを作成し、index.htmlを作成します。ディレクトリ名は自由に決めて問題ありません。また、index.htmlには以下を記述しておきます。

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ナビゲーションメニューのチュートリアル</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <header>

  </header>
</body>
</html>

まずはheader要素にサイトロゴとトップレベルの「ホーム」、「サービス」、「ポートフォリオ」、「お問い合わせ」のメニューをマークアップします。以下がナビゲーションメニューのHTMLの例です。

<header class="header">
  <h1 class="header-logo"><a href="#">LOGO</a></h1>
  <nav class="nav-wrapper">
    <ul class="nav-menu">
      <li class="nav-item"><a href="#">ホーム</a></li>
      <li class="nav-item"><a href="#">サービス</a></li>
      <li class="nav-item"><a href="#">ポートフォリオ</a></li>
      <li class="nav-item"><a href="#">お問い合わせ</a></li>
    </ul>
  </nav>
</header>

この例では、nav内のul要素に「nav-menu」というクラスを定義しています。また、各li要素には「nav-item」というクラスを定義しています。クラス名は一例なので、分かりやすいように適宜変更すると良いです。ポイントとして、各li要素のクラス名は統一しましょう。

続いて、「サービス」のカテゴリーをマークアップします。ul, liなどのリストタグは入れ子にすることができるので、以下のようになります。

<!-- header, h1省略 -->
<nav class="nav-wrapper">
  <ul class="nav-menu">
    <li class="nav-item"><a href="#">ホーム</a></li>
    <li class="nav-item nav-item__submenu">
      <a href="#">サービス</a>
      <ul class="nav-submenu">
        <li class="nav-submenu-item"><a href="#">ウェブデザイン</a></li>
        <li class="nav-submenu-item"><a href="#">モバイルアプリ</a></li>
        <li class="nav-submenu-item"><a href="#">SEOコンサルティング</a></li>
      </ul>
    </li>
    <li class="nav-item"><a href="#">ポートフォリオ</a></li>
    <li class="nav-item"><a href="#">お問い合わせ</a></li>
  </ul>
</nav>

サービス」のli要素に「nav-item__submenu」を追加しています。これは、スタイリングを行う際に他のnav-itemとは別のスタイルを設定するためのクラスです。

また、子要素として「nav-submenu」クラスを定義したul要素を持ち、更にその子要素に「nav-submenu-item」を定義しています。このように、ul, li要素は入れ子構造にすることができます。

ナビゲーションメニューの基本的なHTML構造が完成しました。ウェブブラウザで表示すると以下のようになります。

基本的なスタイリング

ナビゲーションメニューに基本的なスタイリングを適用します。まずはindex.htmlと同じ階層に「style.css」という名前のCSSファイルを作成し、HTMLで読み込ませましょう。

CSSのリセット

ウェブブラウザはデフォルトでユーザーエージェントスタイルシートというCSSが実装されており、これはスタイリングを行う際に邪魔になることが多いです。

例えば、Google Chromeでのul要素のユーザーエージェントスタイルシートの設定は以下です。

ul {
  display: block;
  list-style-type: disc;
  margin-block-start: 1em;
  margin-block-end: 1em;
  margin-inline-start: 0px;
  margin-inline-end: 0px;
  padding-inline-start: 40px;
}

marginやpaddingの位置指定方法については見慣れないかもしれませんが、ul要素には「上下に1emのmargin」、「左に40pxのpadding」がデフォルトで設定されているという解釈で問題ありません。

margin-block-startやmargin-inline-startのような指定は、厳密にはmargin-topやmargin-leftとは違うものです。少し発展的なので詳しい解説は割愛しますが、簡単に述べると、小説のような縦書きで右から左に流れる文章をウェブページで表現する際に余白の方向が自動で切り替わります。

まずはデフォルトのスタイルをリセットし、スタイリングを行いやすいようにします。

/* CSSのリセット */
body {
  margin: 0;
}
ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

ulの他にも、bodyにはデフォルトでmargin: 8px;が設定されているのでこちらもリセットしています。

CSSを書く際は、スタイルの目的などをコメントで記述すると保守性が高くなるのでクセを付けると良いでしょう。

ウェブブラウザで表示してみましょう。以下のようになっていればCSSの読み込みも正常にできていることになります。

基本設定

次に、スタイリングに関わる基本的な設定を行います。一般的にはhtml要素に基本となるfont-sizeを設定したり、body要素にfont-familyやcolorなどを設定したりしますが、今回はデフォルトのままにします。以下のみ記述しましょう。

/* 基本設定 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

box-sizing: border-box;についてよくわからない方は以下の記事で解説しているので参考にしてください。

ナビゲーションメニューの装飾

続いて、ナビゲーションメニューの装飾を行います。

まずはメニューリスト全体にスタイルを追加します。背景色や余白、ボーダーなどを調整して、メニューがウェブサイトになじむようにスタイリングを行います。以下が例です。

/* ナビゲーションメニュー全体のスタイル */
.nav-menu {
  background-color: #333;
  border-radius: 6px;
}

この例では、.nav-menuに対して背景色を設定し、要素の角を6pxずつ丸めています。背景色やボーダーなどは自由に設定しても問題ありません。

次にメニューアイテムにスタイルを追加します。リンクの色、ホバーエフェクト、テキストの余白などを調整し、アイテムがhoverされたときに反応するようにします。

/* ナビゲーションメニューのスタイル */
.nav-item {
  text-align: center;
}

.nav-item a {
  color: #fff;
  display: inline-block;
  padding: 12px;
  text-decoration: none;
  transition: color 0.3s;
}

.nav-item a:hover {
  color: #ff9900;
}

.nav-itemではtext-align: center;で文字を中央寄せにしています。これにより.nav-item内のa要素が中央に配置されます。

.nav-item aでは、文字色と余白の設定をしています。display: inline-blockにする理由は、a要素はデフォルトでdisplay: inlineになっているのでmarginやpaddingなどの余白の設定がうまく機能しません。

また、text-decoration: none;でデフォルトの下線を非表示にし、transitionプロパティを指定してhover時のアニメーションを実装しています。

.nav-item aをhoverすることで、文字色が白から0.3秒かけてオレンジに変化します。

ウェブブラウザで確認すると以下のようになります。

次にサブメニューのスタイルを追加します。サブメニューは初期では非表示にし、サービスをhoverした時にプルダウンメニューとして表示されるように実装します。また、表示の際にtransitionプロパティでアニメーションを付けます。

まずは、表示された状態をスタイリングします。

/* サブメニュー全体のスタイル */
.nav-submenu {
  background-color: #444;
  border-radius: 6px;
}

/* サブメニューのスタイル */
.nav-submenu-item {
  /* 任意で設定してください */
}
.nav-submenu-item a{
  /* 任意で設定してください */
}

.nav-submenu-itemのスタイルは.nav-itemのスタイルを継承しています。また、.nav-submenu-item aのスタイルは、.nav-item aが適用されています。つまり、サブメニューの各項目はナビゲーションメニューと同じ見た目になっています。

サブメニューのスタイルを変更する場合は、.nav-submenu-itemまたは.nav-submenu-item aに記述すると良いでしょう。この例では指定せずに進めます。

ウェブブラウザに表示すると以下のようになります。

プルダウンメニューの実装

続いてサブメニューをプルダウンメニューとして、hover時に表示・非表示を切り替えられるようにします。

まずはサブメニューの高さを確認します。この高さはtransitionでアニメーションさせる際に必要になります。ブラウザの開発ツールを使えば簡単に確認できますが、ここでは要素のフォントサイズや余白から計算してみます。興味がなければ開発ツールで確認してください。

コンテンツの高さを計算してみよう

コンテンツの高さはテキストの場合、font-size、line-height, 上下のpadding、行数で変化します。以下のように計算することができます。

font-size × line-height × 行数 + padding-top + padding-bottom = テキストコンテンツの高さ

.nav-item aはfont-sizeとline-heightの指定をしていないので、デフォルト値であるfont-size: 16pxline-height: 1.5が適用されます。また、padding: 12pxもコンテンツの高さに含まれるので、

16px × 1.5 × 1 + 12px + 12px = 48px

となり、1つの.nav-item aの高さは48pxということが分かります。また、親要素である.nav-submenu-itemは高さの指定をしていないのでデフォルト値のheight: autoによって子要素の高さと同じになります。

.nav-submenu内に.nav-submenu-itemは3つあるので、「48px × 3 = 144px」となり、.nav-submenuの高さ144pxとなることが分かります。

サブメニューの高さが分かったので、次はサブメニューを非表示にします。.nav-submenuに以下を追記しましょう。

/* サブメニュー全体のスタイル */
.nav-submenu {
  background-color: #444;
  border-radius: 6px;
  height: 0;
  overflow: hidden;
}

height: 0;でサブメニューの高さを0にします。子要素は親要素の大きさを超えてもはみ出して表示されるので、overflow: hidden;ではみ出た部分を隠します。結果的にサブメニューは非表示になります。

続いて、hover時に表示させるためのスタイリングを行います。

プルダウンメニューを実装する「サービス」には.nav-item__submenuというクラスを付けたので、このクラスを起点として.nav-submenuを表示させます。

.nav-submenuはheight: 0によって非表示になっているので、heightを指定すれば表示されます。以下のように記述しましょう。

/* サブメニュー全体のスタイル */
.nav-submenu {
  background-color: #444;
  border-radius: 6px;
  height: 0;
  overflow: hidden;
}

/* サブメニューを表示 */
.nav-item__submenu:hover .nav-submenu {
  height: 144px;
}

144pxは先ほど確認した.nav-submenuの値です。最後にtransitionでアニメーションを実装します。

/* サブメニュー全体のスタイル */
.nav-submenu {
  background-color: #444;
  border-radius: 6px;
  height: 0;
  overflow: hidden;
  transition: height 0.3s;
}

transitionによるheightのアニメーションは、変更後の要素の高さがpx指定でないと動作しないので注意しましょう。

記述したらブラウザで確認してみましょう。

問題なく動作すれば基本的なスタイリングは終わりです。

画面サイズでメニューの配置を切り替える

現在のこのナビゲーションメニューの要素は、最下層に配置されているa要素を除くすべてがブロック要素です。これらの要素はwidth: 100%になり、画面いっぱいの横幅を持っています。

このままだとPCやタブレットのような横幅が広いディスプレイでは左右に不要な余白を作ることになるため、このようなデザインは最適ではありません。

横幅が広い場合はメニューを横並びに配置されるようにします。仕様は以下のとおりです。

  • 画面幅768px以上でナビゲーションメニューを横並びにする
  • 画面幅992px以上でロゴとナビゲーションメニューを横並びにする

それでは実装しましょう。

画面幅768px以上でナビゲーションメニューを横並びにする

要素を横に並べるための手法は様々ですが、ここでは最も一般的なフレックスボックスで実装します。フレックスボックスは基準となる要素の子要素に働きます。

display: flex;を指定した要素をフレックスコンテナ、その直下の子要素をフレックスアイテムといいます。

画面幅768px以上で横並びにする要素はナビゲーションメニューなので、.nav-itemを子要素に持つ.nav-menuをフレックスコンテナにします。以下のように記述してください。

/* ナビゲーションメニュー全体のスタイル */
.nav-menu {
  background-color: #333;
  border-radius: 6px;
}

@media screen and (min-width: 768px) {
  .nav-menu {
    display: flex;
    justify-content: space-around;
  }
}

@media screen and (min-width: 768px){}のブロック内にスタイルを記述することで「画面幅768px以上の時」に適用させるスタイルを指定しています。

また、display: flex;とすることで.nav-menuをフレックスコンテナに変更し、justify-content: space-around;で要素を均等配置しています。

ブラウザで表示してみましょう。ウィンドウ幅を変更することで切り替わりを確認できます。

ここでもう1つ確認しなければいけないことがあります。「サービス」にはプルダウンメニューを実装しましたが、今の状態では恐らく想定と違う挙動になっているのではないでしょうか。

プルダウンメニューが表示されると.nav-menuの高さには.nav-submenuの高さが追加されるため、ナビゲーションメニュー全体の高さが変わってしまいます。.nav-menuの高さに影響を与えないようにするにはpositionプロパティを使用します。

/* PC・タブレットでのサブメニューの設定 */
@media screen and (min-width: 768px) {
  .nav-item__submenu {
    position: relative;
  }
  .nav-submenu {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
  }
}

.nav-item__submenuにposition: relativeを指定し、子要素である.nav-submenuにposition: absoluteを指定しています。position: absoluteを指定した要素の大きさは親要素の大きさに反映されなくなるので、.nav-submenuの高さはサブメニュー独自のものになります。

また、leftとtransformにより、position: relativeを指定した.nav-item__submenuの位置を基準にして水平方向に中央配置しています。width: max-contentは、left: 50%によりコンテンツ幅が変わってしまうため子要素の最も幅が広い要素に合わせてwidth指定をしています。

この例では指定していませんが、.nav-submenuにtopプロパティを指定することで縦方向の位置を指定できます。top: 0とすると基準である.nav-item__submenuと同じ位置になり、「サービス」メニューに重なって配置されます。

ここまで記述できたらもう一度ブラウザで確認してみましょう。

これで問題ないかと思います。

画面幅992px以上でロゴとナビゲーションメニューを横並びにする

このままでも大きな問題はないのですが、ロゴとナビゲーションメニューが横並びに配置されるパターンも多いので実装してみましょう。ここでもフレックスボックスを使用します。

この例では、ロゴとナビゲーションメニューを子要素に持つ親要素はheaderになります。header要素にはheaderというクラス名を定義してあるので、以下のように記述します。

/* PC画面のヘッダーのレイアウト */
@media screen and (min-width: 992px) {
  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
}

.headerに対して「画面幅992px以上の時」に適用させるスタイルを記述しています。display: flexで.header-logoと.nav-menuの親要素である.nav-wrapperを横並びにし、space-betweenで両端に配置しています。また、.header-logoと.nav-wrapperの高さが違うと見栄えが悪くなるのでalign-items: centerで垂直中央配置にしています。

ブラウザで確認すると以下のように表示されます。画面幅を広げて見てみましょう。

ナビゲーションメニューの各リンクが小さくて窮屈に見えます。フレックスアイテムはデフォルトではpaddingを含むコンテンツ幅しか持たないので、.headerをフレックスコンテナに変更したことでブロック要素だった.nav-wrapperがフレックスアイテムに変わり、子孫要素である.nav-itemの幅に合わされたためこのようになりました。

これをどのように調整するかは仕様やデザインによるところもあり方法も様々ですが、ここでは必要以上の余白が生まれることを避けるため、ナビゲーションメニューの見た目の担保が取れている768px~991px幅になるように調整します。

/* PC画面のヘッダーのレイアウト */
@media screen and (min-width: 992px) {
  .header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .nav-wrapper {
    width: calc(100% - 224px);
    max-width: 991px;
  }
}

この例では、.nav-wrapperに指定されているwidth: calc(100% – 224%)により、画面幅が992pxになった場合に.nav-wrapperのwidthが「992px – 224px = 768pxとなるように設定しています。

また、画面幅が1px広くなるごとに.nav-wrapperの幅も1pxずつ広くなり、max-width: 991pxの指定によりこれ以上は広がらなくなります。

PC画面でのヘッダーのレイアウトは整いましたが、デザインが施されていないので簡単にスタイリングしておきます。

/* ヘッダーのスタイル */
.header {
  background-color: #444;
  padding: 8px 16px;
}

/* ヘッダーロゴのスタイル */
.header-logo {
  margin: 0;
}
.header-logo a {
  text-decoration: none;
  color: #ff9900;
}

色のバリエーションは多すぎると統一感を損なうので、すでに使用しているダークグレーやオレンジを採用しています。また、h1要素にはデフォルトのmarginが設定されているのでリセットしています。

他の画面サイズでは以下のようになります。

このようになっていればメニューの配置は完成です。

トグルメニューの追加

この時点では、スマホなどの画面幅767px以下のデバイスではナビゲーションメニューが開いた状態になっており、ファーストビューの大半の領域を占有しています。

デザイン上このような仕様であれば問題ありませんが、最近のウェブサイトではトグルメニューを実装し、初期表示ではナビゲーションメニューを閉じておくようにすることが一般的です。

ここではトグルメニューの実装を行います。手順は以下です。

  1. ナビゲーションメニューを非表示
  2. トグルボタンを配置
  3. トグルボタンを機能させる
  4. テスト

また、トグルメニューの仕様は以下です。

  • ボタンの表示は「Menu」とする
  • ボタンの位置はロゴと水平に、ヘッダーの右端に表示
  • 「Menu」をクリックするとナビゲーションメニューが表示される
  • ナビゲーションメニューが開いているときはボタンの表示が「Close」になる
  • 「Close」をクリックするとナビゲーションメニューが閉じる
  • 画面幅768px以上ではボタンを非表示

それでは始めましょう。

ナビゲーションメニューを非表示

まずはナビゲーションメニューを非表示にします。画面幅768px以上では非表示にする必要はないので、合わせて設定します。

/* ナビゲーションメニュー全体のスタイル */
.nav-menu {
  background-color: #333;
  border-radius: 6px;

  height: 0;
  overflow: hidden;
  transition: height 0.3s;
}

@media screen and (min-width: 768px) {
  .nav-menu {
    display: flex;
    justify-content: space-around;
    height: auto;
    overflow: visible;
  }
}

.nav-menuの非表示は、サブメニューを非表示にした時と同じ方法で行っています。これにより、サブメニューと同じアニメーションを実装できるようになります。

また、画面幅768px以上ではデフォルト値のheight: auto、overflow: visible;を追加し、これまでどおりに表示されるようにしています。

サービス」のプルダウンメニューは画面幅が768px以上の時にpositionプロパティを使用しており、.nav-menuをはみ出して表示させています。overflow: hidden;のままだとプルダウンメニューが表示されなくなってしまうので注意が必要です。

画面幅768px未満では以下のように表示されます。

トグルボタンを配置

次にトグルボタンを配置します。

HTMLに追加するのが一番簡単かと思うので、以下のように追記してください。

<header class="header">
  <h1 class="header-logo"><a href="#">LOGO</a></h1>
  <nav class="nav-wrapper">
    <!-- .nav-menu省略 -->
  </nav>
  <div class="toggle-btn"></div>
</header>

toggle-btnというクラス名を付けた空のdiv要素を追加しました。

仕様ではトグルボタンの表示テキストは「Menu」なのでdiv内にMenuと記述しても実装できますが、スクリーンリーダーを使用しているユーザーを考慮すると、意味のない要素をMenuと読みあげられても混乱させるだけなのでアクセシビリティ的に良くありません。

Menu」や「Close」のテキストは疑似要素を使用して表示させることにします。.nav-wrapperのスタイルの後くらいに記述しておくと良いでしょう。

/* トグルボタンのスタイル */
.toggle-btn::before {
  content: "Menu";
}
.toggle-btn::after {
  content: "Close";
  display: none;
}

Close」はdisplay: none;で非表示にしておきます。疑似要素についてはこちらの記事で解説しているので、興味のある方は参考にしてください。

以下のように表示されます。

.toggle-btnはdivタグを使用しており、divはブロック要素なのでLOGOの下に表示されます。

仕様では「ボタンの位置はロゴと水平に、ヘッダーの右端に表示」となっているので、そのように実装します。

要素の配置はフレックスボックスを使用します。まずは.header-logoと.toggle-btnの親要素である.headerをフレックスコンテナにし、子要素の位置を調整します。

/* ヘッダーのスタイル */
.header {
  background-color: #444;
  padding: 8px 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

/* タブレット画面のヘッダーのレイアウト */
@media screen and (min-width: 768px) and (max-width: 991px) {
  .header {
    display: block;
  }
}

/* PC画面のヘッダーのレイアウト */
@media screen and (min-width: 992px) {
  /* 以下は重複しているので削除します */
  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .nav-wrapper {
    width: calc(100% - 224px);
    max-width: 991px;
  }
}

justify-content: space-between;で子要素を両端に配置し、align-items: centerで上下中央揃えにしています。PC用のレイアウトの指定と重複するので、PC側の.headerのスタイルは削除します。

また、画面幅768px以上、992px未満ではロゴとナビゲーションメニューは縦に並ぶようにしたいので、@media screen and (min-width: 768px) and (max-width: 991px){}ブロック内で.headerをブロック要素に戻しています。メディアクエリはこのように複数条件も指定できるので覚えておくと良いでしょう。

これで「ボタンの位置はロゴと水平に、ヘッダーの右端に表示」という仕様どおりになりました。

次にトグルボタンのスタイリングを行います。現状ではただのテキストなので、ボタンであるということをユーザーが認識できるようにデザインします。仕様にはありませんが、「Menu」と「Close」でスタイルを変えるとボタンの機能が変化したことをユーザーが分かりやすくなるので、そのように実装します。

まずは「Menu」と「Close」の共通のスタイルを設定します。

/* トグルボタンのスタイル */
.toggle-btn:before,
.toggle-btn:after {
  padding: 4px 8px;
  border: 2px solid #ff9900;
  border-radius: 4px;
  font-weight: bold;
}

次に、「Menu」と「Close」それぞれのスタイルを設定します。

/* トグルボタンのスタイル */
.toggle-btn:before,
.toggle-btn:after {
  /* 省略 */
}

.toggle-btn:before {
  content: "Menu";
  display: inline-block;
  color: #ff9900;
}
.toggle-btn:after {
  content: "Close";
  display: none;
  color: #444;
  background-color: #ff9900;
}

Menu」は文字色をオレンジ、背景色は指定していないので透過されます。「Close」では文字色と背景色を反転させています。デザインを変えすぎると違和感が生まれるので注意しましょう。

また、疑似要素はデフォルトではインライン要素になるので、paddingやmarginなどの余白の設定がうまく機能しません。そのため、「Menu」にはdisplay: inline-block;を設定してブロック要素の特徴を追加しています。

以下のように表示されます。

.toggle-btn:beforeをdisplay: none;に変更し、.toggle-btn:afterをdisplay: inline-block;とすることで「Close」の確認もできます。

これでトグルボタンの配置とスタイリングができました。

トグルボタンを機能させる

現状ではトグルボタンをクリックしても何も起きません。トグルメニューの仕様でまだ実装できていない機能は下記です。

  • 「Menu」をクリックするとナビゲーションメニューが表示される
  • ナビゲーションメニューが開いているときはボタンの表示が「Close」になる
  • 「Close」をクリックするとナビゲーションメニューが閉じる

これを機能させる方法はいろいろ考えられますが、あまりトリッキーなことはせずに基本的なJavaScriptで実装してみましょう。下記の手順で行います。

  1. JavaScriptファイルの作成
  2. JavaScriptで.nav-wrapperに「show」クラスを追加
  3. CSSで.nav-wrapper.showセレクタを起点にナビゲーションメニューを表示

それでは始めましょう。

JavaScriptファイルの作成

まずは「js」ディレクトリを作成し、その中に「navigation.js」というファイル名のJavaScriptファイルを作成します。

index.html
style.css
js
 |- navigation.js

次に、index.htmlでnavigation.jsを読み込みます。head内にscriptタグを記述する場合は、defer属性がないとHTMLの要素をJavaScriptで取得することができないので注意しましょう。

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ナビゲーションメニューのチュートリアル</title>
  <link rel="stylesheet" href="style.css">
  <script src="js/navigation.js" defer></script>
</head>

これでトグルボタンを機能させる準備が整いました。

avaScriptで.nav-wrapperに「show」クラスを追加

続いて、navigation.jsに以下のコードを記述します。

const toggleBtn = document.querySelector('.toggle-btn');
const navMenu = document.querySelector('.nav-wrapper');

toggleBtn.addEventListener('click', () => {
  navMenu.classList.toggle('show');
});

簡単に解説します。

const toggleBtn = document.querySelector('.toggle-btn');
const navMenu = document.querySelector('.nav-wrapper');

toggleBtn.addEventListener('click', () => {
  navMenu.classList.toggle('show');
});

ハイライトの箇所では、「toggleIcon」と「navMenu」という定数を定義し、それぞれに.toggle-btnと.nav-wrapperの要素を格納しています。これにより、トグルボタンとナビゲーションメニューをJavaScriptで扱えるようになります。

const toggleBtn = document.querySelector('.toggle-btn');
const navMenu = document.querySelector('.nav-wrapper');

toggleBtn.addEventListener('click', () => {
  navMenu.classList.toggle('show');
});

ここでは、toggleBtnにクリックイベントを設定しています。{}のブロック内にはトグルボタンをクリックした時の処理を記述します。

const toggleBtn = document.querySelector('.toggle-btn');
const navMenu = document.querySelector('.nav-wrapper');

toggleBtn.addEventListener('click', () => {
  navMenu.classList.toggle('show');
});

.nav-wrapperに設定されているクラス名を取得し、「show」というクラスがあれば外し、なければ追加するという処理を行っています。これはトグルボタンをクリックする度に実行されます。

CSSで.nav-wrapper.showセレクタを起点にナビゲーションメニューを表示

最後にナビゲーションメニュー表示後のスタイリングを行います。まずはJavaScriptが正常に動作しているかの確認も含めて、ナビゲーションメニューの表示のみを設定します。

ナビゲーションメニューの非表示は.nav-menuのheight: 0;によって行いました。なので、.nav-wrapper.showを起点に.nav-menuのheightを変更します。

/* トグルメニューの実装 */
.nav-wrapper.show .nav-menu {
  height: 100vh;
}

height: auto;だとアニメーションが機能しないのでheight: 100vh;としています。100vhは簡単に説明すると「ブラウザの表示領域の100%の高さ」という指定です。

Menu」をクリックすると、JavaScriptが正しく機能していれば以下のようになります。

スタイルが崩れていますが正しく実行されています。変化しない場合は、JavaScriptのコードに誤りがないか(スペルミス、スペース有無、全角半角、大文字小文字などを確認)、HTMLのscriptタグにdefer属性が付いているかを確認してください。

ブラウザの開発ツール(F12キー)でコンソールを表示させるとエラーを特定できます。以下はdocument.querySelector(‘.toggle-btn’);をdocument.querySelector(‘toggle-btn’);と記述した時のエラーです。

navigation.jsファイルの4行目に問題があることが分かります。エラーの内容は、toggleBtnという定数の値がnullなのでaddEventListenerが使用できないというものです。document.querySelectorはCSSのセレクタで指定する必要があるので、「.toggle-btn」を「toggle-btn」とすると要素を取得できません。

ナビゲーションメニューのスタイルが崩れているので、まずはこれを調整します。

現状の問題は以下のとおりです。

  • .headerが.nav-menuの高さの影響で.header自身も画面いっぱいの高さになっている
  • .headerにalign-items: center;を指定しているため、.headerの高さが変化することでロゴとトグルボタンの位置も変わる
  • .nav-wrapperが.headerのフレックスアイテムになっているため、HTMLの構造と.headerのjustify-content: space-between;によりナビゲーションメニューが中央に配置されている

これらの問題はpositionプロパティを使用することで解決できます。まずは、.headerにposition: relative;を設定します。

.header {
  background-color: #444;
  padding: 8px 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
}

次に、.nav-wrapperにposition: absolute;を設定します。

/* トグルメニューの実装 */
.nav-wrapper {
  position: absolute;
  top: 60px;
  right: 0;
}

.nav-wrapper.show .nav-menu {
  height: 100vh;
}

.headerの高さはロゴの高さが基準になっています。ロゴはh1であり、デフォルトのfont-sizeは2emです。h1の先祖要素にはfont-sizeを指定していないので、2emはhtmlのデフォルト値である16pxの二倍の32pxになります。先述の例によって計算すると、

32×1.5×1+8+8=64px

となり、.headerの高さが分かります。nav-wrapperの位置はtop: 60px;となっているので、.headerの下から4px重なったところに配置されます。

ブラウザで表示すると以下のようになります。

トグルボタンが「Menu」のままなので、「Close」になるように設定します。以下を記述してください。

/* トグルメニューの実装 */
.nav-wrapper {
  /* 省略 */
}

.nav-wrapper.show .nav-menu {
  /* 省略 */
}

.nav-wrapper.show + .toggle-btn:before {
    display: none;
}

.nav-wrapper.show + .toggle-btn:after {
    display: inline-block;
}

以下のように表示されれば完成です。

テスト

トグルボタンの実装は完成しましたが、まだ実装できていない仕様が残っています。

画面幅768px以上ではボタンを非表示

現状では、画面幅を広げると以下のように表示されます。

問題は以下のとおりです。

  • トグルボタンが表示されている
  • .nav-wrapperにposition: absolute;が指定されているため配置が変わっている

根本的な問題が分かれば修正は簡単です。以下を記述すれば修正されるでしょう。

@media screen and (min-width: 768px) {
  .nav-wrapper {
    position: static;
  }

  .toggle-btn {
    display: none;
  }
}

position: static;によって.nav-wrapperのpositionプロパティの値をデフォルト値に戻しています。

問題を理解しなくても修正できますが、無理やり修正しようとすると保守性が損なわれる可能性があるので注意しましょう。以下は.nav-wrapperのposition: absolute;を解除せずに修正を行った場合の例です。

/* 悪い例 */
@media screen and (min-width: 768px) {
  .toggle-btn {
      display: none;
  }

  .header {
      height: 112px;
  }

  .nav-wrapper {
      width: calc(100% - 32px);
      right: 16px;
      top: auto;
      bottom: 8px;
  }
}

@media screen and (min-width: 992px) {
  .header {
      height: auto;
  }

  .nav-wrapper {
      width: calc(100% - 224px);
  }
}

この例はほとんど問題なくブラウザで表示されますが、コードの記述量が明らかに多いです。また、position: absolute;の設定を意味のないところに残しておくと、表示崩れの原因になります。

positionプロパティを使用する場合は複雑なコードになりやすいので、初心者のうちは特に気を付けましょう。

不具合の修正

これで仕様どおりのトグルメニューの実装ができましたが、一点だけ分かりやすい不具合があります。

トグルメニューを開いた状態でウィンドウ幅を広げると、ナビゲーションメニューの高さが画面全体に広がっています。

原因は.nav-wrapperにshowクラスが付いたままなので.nav-menuのheightの値が100vhになっているところにあります。修正の方法は二通りあります。

  • 画面幅768px以上で.nav-wrapper.show .nav-menuのheightを変更する
  • 画面幅768px以上で.nav-wrapperからshowクラスを外す

高さを変更する方が簡単なのでこちらの方法を採用します。以下を記述するだけです。

@media screen and (min-width: 768px) {
  .nav-wrapper {
    position: static;
  }

  .toggle-btn {
    display: none;
  }

  .nav-wrapper.show .nav-menu {
    height: auto;
  }
}

ブラウザで表示して確認してみましょう。修正されたことが分かるかと思います。

二つの修正後の動作は厳密には違いがあります。heightを変更する方法では.showクラス自体はそのまま.nav-wrapperに残るので、

  1. 画面幅768px未満でトグルメニューを表示
  2. 画面幅を768px以上に広げる
  3. 画面幅を768px未満に戻す

としたとき、トグルメニューは開いた状態になります。

一方で、showクラスを外す方法では、画面幅を戻したときにトグルメニューが閉じた状態になります。どちらを採用するかは仕様や好みにもよりますが、クラスの付け外しはJavaScriptの知識が必要になります。HTML/CSSのコーディングに慣れたらJavaScriptにも触れてみると良いでしょう。

あと、不具合ではないですがトグルボタンは空のdiv要素なので、デフォルトではaタグのようにhoverしたときにマウスカーソルが変更されません。PC環境でウィンドウ幅を768px以下にしてブラウジングするユーザーもいるかと思うので対応しておきましょう。

/* トグルボタンのスタイル */
.toggle-btn:hover {
    cursor: pointer;
}

.toggle-btn:before,
.toggle-btn:after {
    /* 省略 */
}

他にもユーザビリティやアクセシビリティを考慮すると修正が必要なところもありますが、少し発展的なのでここでの説明は割愛します。興味のある方は以下の点を参考にブラウザテストを行うと良いでしょう。

  • いろいろなブラウザで表示する
  • ブラウザの設定で文字の大きさを変更する

サンプルコードの全体

この記事で作成したナビゲーションメニューの最終的なサンプルコードは以下です。

HTML

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>ナビゲーションメニューのチュートリアル</title>
  <link rel="stylesheet" href="style.css">
  <script src="js/navigation.js" defer></script>
</head>

<body>
  <header class="header">
    <h1 class="header-logo"><a href="#">LOGO</a></h1>
    <nav class="nav-wrapper">
      <ul class="nav-menu">
        <li class="nav-item"><a href="#">ホーム</a></li>
        <li class="nav-item nav-item__submenu">
          <a href="#">サービス</a>
          <ul class="nav-submenu">
            <li class="nav-submenu-item"><a href="#">ウェブデザイン</a></li>
            <li class="nav-submenu-item"><a href="#">モバイルアプリ</a></li>
            <li class="nav-submenu-item"><a href="#">SEOコンサルティング</a></li>
          </ul>
        </li>
        <li class="nav-item"><a href="#">ポートフォリオ</a></li>
        <li class="nav-item"><a href="#">お問い合わせ</a></li>
      </ul>
    </nav>
    <div class="toggle-btn"></div>
  </header>
</body>
</html>

CSS

/* CSSのリセット */
body {
  margin: 0;
}
ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* 基本設定 */
*,
*::before,
*::after {
  box-sizing: border-box;
}

/* ヘッダーのスタイル */
.header {
  background-color: #444;
  padding: 8px 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
}

/* タブレット画面のヘッダーのレイアウト */
@media screen and (min-width: 768px) and (max-width: 991px) {
  .header {
    display: block;
  }
}

/* PC画面のヘッダーのレイアウト */
@media screen and (min-width: 992px) {
  .nav-wrapper {
    width: calc(100% - 224px);
    max-width: 991px;
  }
}

/* ヘッダーロゴのスタイル */
.header-logo {
  margin: 0;
}
.header-logo a {
  text-decoration: none;
  color: #ff9900;
}

/* ナビゲーションメニュー全体のスタイル */
.nav-menu {
  background-color: #333;
  border-radius: 6px;
  height: 0;
  overflow: hidden;
  transition: height 0.3s;
}

@media screen and (min-width: 768px) {
  .nav-menu {
    display: flex;
    justify-content: space-around;
    height: auto;
    overflow: visible;
  }
}

/* ナビゲーションメニューのスタイル */
.nav-item {
  text-align: center;
}

.nav-item a {
  color: #fff;
  display: inline-block;
  padding: 12px;
  text-decoration: none;
  transition: color 0.3s;
}

.nav-item a:hover {
  color: #ff9900;
}

/* サブメニュー全体のスタイル */
.nav-submenu {
  background-color: #444;
  border-radius: 6px;
  height: 0;
  overflow: hidden;
  transition: height 0.3s;
}

/* サブメニューを表示 */
.nav-item__submenu:hover .nav-submenu {
  height: 144px;
}

/* サブメニューのスタイル */
.nav-submenu-item {
  /* 任意で設定してください */
}
.nav-submenu-item a{
  /* 任意で設定してください */
}

/* PC・タブレットでのサブメニューの設定 */
@media screen and (min-width: 768px) {
  .nav-item__submenu {
    position: relative;
  }
  .nav-submenu {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: max-content;
  }
}

/* トグルメニューの実装 */
.nav-wrapper {
  position: absolute;
  top: 60px;
  right: 0;
}

.nav-wrapper.show .nav-menu {
  height: 100vh;
}

.nav-wrapper.show + .toggle-btn::before {
  display: none;
}

.nav-wrapper.show + .toggle-btn::after {
  display: inline-block;
}

/* トグルボタンのスタイル */
.toggle-btn:hover {
  cursor: pointer;
}
.toggle-btn::before,
.toggle-btn::after {
  padding: 4px 8px;
  border: 2px solid #ff9900;
  border-radius: 4px;
  font-weight: bold;
}

.toggle-btn::before {
  content: "Menu";
  display: inline-block;
  color: #ff9900;
}

.toggle-btn::after {
  content: "Close";
  display: none;
  color: #444;
  background-color: #ff9900;
}

@media screen and (min-width: 768px) {
  .nav-wrapper {
    position: static;
  }

  .toggle-btn {
    display: none;
  }

  .nav-wrapper.show .nav-menu {
    height: auto;
  }
}

JavaScript

const toggleBtn = document.querySelector('.toggle-btn');
const navMenu = document.querySelector('.nav-wrapper');

toggleBtn.addEventListener('click', () => {
  navMenu.classList.toggle('show');
});

最後に

ナビゲーションメニューの作成を通じてウェブ制作やHTML/CSSの基本について解説しました。

ナビゲーションメニューはウェブサイトにおいてとても重要な要素ですが、機能的なナビゲーションメニューは実装の難易度も高くなります。初心者のうちはナビゲーションメニューの作成にとても苦労すると思いますので、なるべく詳しい解説になるよう心掛けました。

このようなパーツ単位でのコーディングは作業効率が悪いですが、実際の制作現場ではナ、ビゲーションメニューのような重要な要素はベテランのコーダーがあらかじめ作っておくというケースもあります。

サンプルコードのJavaScriptで記述したトグルボタンの機能は、指定した要素のクラス名を付け外ししているだけの処理です。細かいところに拘らなければif文やfor文のようなプログラミングの基礎が分からなくても実装できます。

document.querySelector()の引数を変更することで違う要素も取得でき、アコーディオンメニューやモーダルウィンドウの実装などにも応用できます。

プログラミングの学習は作りたいものが見つからないとモチベーションが上がらないという方も多いので、短くて単純なコードで動作するようなプログラムを実装してみるところからJavaScriptに慣れていくのも良い学習方法だと思います。

タイトルとURLをコピーしました