グローバルメニューを多階層メニューに 第2階層をスライドダウン、第3階層をアイコンでアコーディオン開閉の併用

ヘッダーの第1階層のメニューをマウスオーバーすると第2階層メニューがスライドダウンし、第2階層メニューのアイコンクリックで、第2階層メニュー自体のリンクに遷移することなく、第3階層メニューをアコーディオン開閉する(クリックしていないメニューの方は自動で閉じる)、多階層メニューを作成しました。
備忘録としてcodepenにも登録してみましたので、ご参考になれば幸いです。

使用するファイル

  • HTMLファイル
  • CSSファイル
  • JSファイル

事前準備

  • jQuery及びご利用のJSファイル、CSSファイルをHTMLファイルで読み込みます(詳細説明割愛)。
  • Font Awesomeに登録し、同じくHTMLファイルで読み込みます。

Font Awesomeの登録方法は下記のとおり簡単です。

Font Awesome のページで「Start for Free」を選択後、メールアドレスを登録します。
メール認証、パスワード設定と進んでいくと、コードが発行されます。
これをheadタグ内のscriptタグ内で使用します。

縦に真っすぐ並べるパターン

第3階層まで開閉した時のイメージ

slidedown_1

CODEPENで実装と確認

「Run Pen」をクリックしてください。

See the Pen dropdown&accordion by blue moon (@blue-moon) on CodePen.

HTMLファイル

<head>
  <!-- 省略 -->
  <script src="https://kit.fontawesome.com/●●●●●●●●●●.js" crossorigin="anonymous"></script> <!-- ●●●部分にはご自身のfontawesomeのキーを入力 -->
  <link rel="stylesheet" href="style.css">  <!-- ご自身のパスに変更 -->
</head>
<body>
  <header>
    <nav>
      <!-- 第1階層のグローバルメニュー -->
      <ul class="main-menu-list">
        <li class="main-menu">
          <p>menu1<i class="fas fa-angle-down"></i></p>
          <!-- 第2階層メニュー -->
          <ul class="category">
            <li>
              <a href="#">category1</a>
              <!-- 第3階層メニュー開閉のためのクリック用アイコン -->
              <span><i class="fas fa-minus"></i></span>
              <!-- 第3階層メニュー -->
              <ul class="item-list">
                <li><a href="#">item1</a></li>
                <li><a href="#">item2</a></li>
                <li><a href="#">item3</a></li>
              </ul>
            </li>
            <li>
              <a href="#">category2</a>
              <span><i class="fas fa-minus"></i></span>
              <ul class="item-list">
                <li><a href="#">item1</a></li>
                <li><a href="#">item2</a></li>
                <li><a href="#">item3</a></li>
              </ul>
            </li>
          </ul>
        </li>  
        <li class="main-menu">
          <p>menu2<i class="fas fa-angle-down"></i></p>
          <ul class="category">
            <li>
              <a href="#">category1</a>
              <span><i class="fas fa-minus"></i></span>
              <ul class="item-list">
                <li><a href="#">item1</a></li>
                <li><a href="#">item2</a></li>
                <li><a href="#">item3</a></li>
              </ul>
            </li>
            <li>
              <a href="#">category2</a>
              <span><i class="fas fa-minus"></i></span>
              <ul class="item-list">
                <li><a href="#">item1</a></li>
                <li><a href="#">item2</a></li>
                <li><a href="#">item3</a></li>
              </ul>
            </li>
          </ul>
        </li>
        <li class="main-menu">menu3</li>
        <li class="main-menu">menu4</li>
      </ul>
    </nav>
  </header>
</body>

CSSファイル

/* reset&base */
body {
  font-size: 12px;
  color: #666;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

li {
  line-height: 2.2;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

nav {
  margin: 20px auto;
  width: 80%;
}

/* 第1階層 */
.main-menu-list {
  width: 100%;
  display: flex;
}

.main-menu {
  margin: 0 2px;
  height: min-content;
  width: calc(100% / 4);
  text-align: center;
  background-color: #e8d3ff;
  cursor: pointer;
}

.fa-angle-down {
  padding-left: 5px;
  cursor: pointer;
}

/* 第2階層 */
.category {
  padding: 0;
}

.category > li {
  background-color: #c6cfff;
  font-size: 0.9em;
}

.category li span {
  position: relative;
  cursor: pointer;
}

.fa-minus {
  padding-left: 0;
}

.category li span::after {
  position: absolute;
  top: calc(0.25em * 1.1);
  left: calc(50% - 0.1em);
  width: 0.2em;
  height: 0.875em;
  content: "";
  background-color: #666;
  transition: 0.3s ease-in-out;
  border-radius: 0.1em;
}

.close::after {
  transform: rotate(90deg);
}

/* 第3階層 */
.item-list > li {
  background-color: #deecff;
}

補足説明

マウスオーバーのターゲットとなる第1階層メニューは{display: flex}で横並びにしていますが、子要素はmarginで少し間隔を取っていまず。理由は、マウスが第1階層を横に動いた時に、マウスが離れず次のhoverイベントが発動しないためです。

JSファイル

$(window).on('load resize', function() { //読み込み時とリサイズ時
  const cateList = $('header .category');
  const itemList = $('header .item-list');
  const moreIcon = $('.category li span');
  cateList.hide();
  itemList.hide(); 
  
  //第1階層メニューをマウスオーバーで第2階層メニューをスライドダウン
  $('nav .main-menu').hover(function() {
    $(this).children(cateList).not(':animated').slideDown(300);
  },function() {
    moreIcon.removeClass('close');
    $(itemList).slideUp(300);
    $(cateList).slideUp(300);
  });
    
  //第2階層メニューのアイコンクリックで第3階層メニューをアコーディオン開閉
  $(moreIcon).click(function() {
    $(this).next(itemList).not(':animated').slideToggle(300);
    
    if ($(this).hasClass('close')) {
      moreIcon.removeClass('close');
    }else{  
      $(this).addClass('close');
    }
    
    moreIcon.not($(this)).removeClass('close');
    moreIcon.not($(this)).next(itemList).slideUp(300);
  });
});

補足説明

第2階層のメニューアイコンをtoggleClassを使ってクラスを追加・削除してしまうと、開閉している方のメニューを閉じるアイコンに反応しないため、条件分岐でクラスを追加・削除しています。

第2階層をスライドダウンする、$(‘nav .main-menu’).hover(function() { ~ の中に、第3階層をアコーディオン開閉処理を入れると正常に動作するように見えますが、クラスの付与が動作しないことがありますので、入れ子にしない方がいいと思います。

また、hover()メソッドをmouseover()メソッドとmouseout()メソッドの組み合わせに変えてみたり、.not(‘:animated’)をstop()メソッドに変えてみたりしましたが、どうも表示や動作がうまくいかない所があり、今のところ上述のコードが一番良い方法かなと思っています。

少しずつメニューが横にずれるパターン

第3階層まで開閉した時のイメージ

slidedown_2

CODEPENで実装と確認

See the Pen dropdown&accordion_2 by blue moon (@blue-moon) on CodePen.

CSSファイル

/* reset&base */
body {
  font-size: 12px;
  color: #666;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
  box-sizing: border-box;
}

li {
  line-height: 2.2;
  box-sizing: border-box;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

nav {
  margin: 20px auto;
  width: 80%;
}

/* 第1階層 */
.main-menu-list {
  width: 100%;
  display: flex;
}

.main-menu {
  margin: 0 1px; /* 間隔をあけること */
  height: min-content;
  width: calc(100% / 4);
  text-align: center;
  background-color: #e8d3ff;
  cursor: pointer;
  border: solid 1px #fff;
}

.fa-angle-down {
  padding-left: 5px;
  cursor: pointer;
}

/* 第2階層 */
.category {
  padding: 0;
  width: calc(100% + 2px);
  border: solid 2px #fff;
  transform: translate(10px, 0);
}

.category > li {
  background-color: #c6cfff;
  font-size: 0.9em;
}

.category li span {
  position: relative;
  cursor: pointer;
}

.fa-minus {
  padding-left: 0;
}

.category li span::after {
  position: absolute;
  top: 0.25em;
  left: calc(50% - 0.1em);
  width: 0.2em;
  height: 0.875em;
  content: "";
  background-color: #666;
  transition: 0.3s ease-in-out;
  border-radius: 0.1em;
}

.close::after {
  transform: rotate(90deg);
}

/* 第3階層 */
ul.item-list {
  width: calc(100% + 4px);
  border: solid 2px #fff;
  transform: translate(10px, 0);
}

.item-list > li {
  background-color: #deecff;
}

.item-list > li:not(:last-child) {
  border-bottom: solid 0.1em #fff;
}

補足説明

HTML、JSファイルは前述のパターンと同じです。
CSSのみ少しずつ手を加えています。

最後に

ヘッダーのメニューの作成って、以外に手こずるのは私だけでしょうか。
今回はスライドダウンだけで他に装飾はしていませんが、それ以外にフィックスしてみたり、スクロールしたら下りてきたり、currentクラスをつけたり、hoverやactiveの時のスタイルを変更したり、モバイルとPCで全く別のものを作ったり…、サイトの冒頭部分だからこそ拘りたくなるものの、ハマるときりがないです。
サイト作成の一番最初に取り掛かって完成したつもりでも、その後コンテンツを作っていく過程でしっくりいかず、何度も変更したりしてスパっと決まらずいつも悩まされます。

※後日追記
この多階層メニューを3D回転させてみました。

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