メニューを押すと左へ移動してサブメニューを開くメニュー

横に並んだメニューを押すと、そのメニューが一番左へ移動し、下にサブメニューが開く方法。開くのは1つだけで、速さは数字の変更だけで調整できる。本サイトもこれを装着中。

動きは次のとおり。

状態押すと
閉じているメニューそのメニューが一番左へすべり、ほかのメニューは右へ順につめる。下にサブメニューが開く
別のメニューが開いているとき開いていたサブメニューは閉じながら、押したメニューのサブメニューが開く
開いているメニューサブメニューが閉じる(並びはそのまま)

※ 開いているメニューは太字になる。 ※ jQuery が必要(WordPress なら同梱の jQuery でよい)。WordPress でなくても動く。

HTML

メニューを出したい場所に配置。

<!-- 押したメニューが一番左へ移動し、下にサブメニューが開く(開くのは1つだけ) -->
<nav class="hn" aria-label="記事の分類">
  <div class="hn-bar">
    <button type="button" class="hn-btn" aria-expanded="false" aria-controls="hn-categories">CATEGORIES</button>
    <button type="button" class="hn-btn" aria-expanded="false" aria-controls="hn-archives">ARCHIVES</button>
    <button type="button" class="hn-btn" aria-expanded="false" aria-controls="hn-tags">TAGS</button>
  </div>
  <div class="hn-panel" id="hn-categories" hidden>
    <ul>
      <li><a href="/category/css/">CSS</a></li>
      <li><a href="/category/jquery/">JQUERY</a></li>
      <li><a href="/category/php/">PHP</a></li>
      <li><a href="/category/wordpress/">WORDPRESS</a></li>
    </ul>
  </div>
  <div class="hn-panel" id="hn-archives" hidden>
    <ul>
      <li><a href="/2026/">2026</a></li>
      <li><a href="/2025/">2025</a></li>
      <li><a href="/2024/">2024</a></li>
    </ul>
  </div>
  <div class="hn-panel" id="hn-tags" hidden>
    <ul>
      <li><a href="/tag/google/">#GOOGLE</a></li>
      <li><a href="/tag/html-css/">#HTML/CSS</a></li>
      <li><a href="/tag/plugins/">#PLUGINS</a></li>
    </ul>
  </div>
</nav>

※ ボタンの aria-controls と、サブメニューの id は同じ名前にする。JavaScript は aria-controls の名前で開くサブメニューを探している。
※ サブメニューの hidden は、JavaScript が動かないとき用。JavaScript が動くと外れる。

CSS

<head> 内に配置。<noscript> もセットで置く。

<style>
/* ===== ここから CSS ===== */
.hn {
  --hn-size: 18px;        /* ★ メニュー・サブメニューの文字の大きさ */
  --hn-weight: 500;       /* ★ メニューの太さ */
  --hn-open-weight: 700;  /* ★ 開いているメニューの太さ */
  --hn-hover: #ccccc8;    /* ★ マウスを乗せたときの色 */
}
/* メニュー(横に3つ並ぶボタン) */
.hn-bar {
  font-size: var(--hn-size);
  font-weight: var(--hn-weight);
  line-height: 1.2;
}
.hn-btn {
  padding: 0;
  margin: 0 0.5em 0 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: color 0.5s;
}
.hn-btn:hover {
  color: var(--hn-hover);
}
/* 開いているメニューは色はそのままで太く */
.hn-btn[aria-expanded="true"] {
  font-weight: var(--hn-open-weight);
}
.hn-btn[aria-expanded="true"]:hover {
  color: inherit;
}
/* サブメニュー(横に並べる) */
.hn-panel ul {
  list-style: none;
  margin: 0;
  padding: 6px 0 0; /* メニューとの間。開閉の動きに含めるため、外側の margin ではなく内側の padding で取る */
  font-size: var(--hn-size);
}
.hn-panel li {
  display: inline-block;
  margin-right: 0.5em;
  font-weight: 300;
}
.hn-panel a {
  color: inherit;
  text-decoration: none;
}
/* ===== ここまで CSS ===== */
</style>
<!-- JavaScript が動かない環境では、サブメニューを最初から全部開いておく -->
<noscript><style>.hn-panel[hidden] { display: block; }</style></noscript>

※ サブメニューとメニューの間は、ul の padding-top で取っている。外側の margin にすると、閉じきった瞬間にその分だけカクッと詰まる。

JavaScript

</body> の直前に配置(WordPress の場合は下の「WordPress で使う場合」の方法で読み込む)。

<!-- jQuery(すでに jQuery を利用している場合は、この1行は不要) -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
/* ===== JavaScript =====
 * ・メニューを押す:そのメニューが一番左へ移動し、下にサブメニューが開く(ほかのメニューは右へ順につめる)
 * ・別のメニューを押す:開いていたサブメニューは閉じ、押したほうが開く(開くのは1つだけ)
 * ・同じメニューを押す:サブメニューが閉じる(並びはそのまま)
 * ・「動きを減らす」設定の人には、動きなしで開け閉めする
 */
(function ($) {
  'use strict';

  var SPEED = 450; /* ★ 開閉・移動の速さ(ミリ秒)。大きいほどゆっくり */

  var reduce = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  var speed  = reduce ? 0 : SPEED;
  var bar    = document.querySelector('.hn-bar');
  var $btns  = $('.hn-btn');

  // hidden 属性(JavaScript が動かないとき用)を、jQuery で動かせる形(display: none)に置き換える
  $('.hn-panel').removeAttr('hidden').hide();

  // サブメニューを開く:今の高さ → 開ききった高さ へ動かす
  // (slideDown だと、すばやく続けて押したときに高さが途中で止まることがあるため、高さを自分で決めて動かす)
  function openPanel($p) {
    var from = $p.is(':visible') ? $p.height() : 0;
    $p.stop(true).css({ display: 'block', overflow: 'hidden', height: '' });
    var to = $p.height();
    $p.css('height', from).animate({ height: to }, speed, function () {
      $p.css({ height: '', overflow: '' });
    });
  }

  // サブメニューを閉じる:今の高さ → 0 へ動かす
  function closePanel($p) {
    if (!$p.is(':visible')) { return; }
    $p.stop(true).css({ overflow: 'hidden', height: $p.height() }).animate({ height: 0 }, speed, function () {
      $p.hide().css({ height: '', overflow: '' });
    });
  }

  // メニューの並び・太さが変わるとき、前の位置から今の位置へすべらせる(FLIP という手法)
  function animateBar(change) {
    var items  = Array.prototype.slice.call(bar.children);
    var before = items.map(function (el) { return el.getBoundingClientRect().left; });

    change(); // 並べ替え・太さの切り替え(一瞬で)

    if (!speed) { return; }
    items.forEach(function (el, i) {
      var dx = before[i] - el.getBoundingClientRect().left;
      if (!dx) { return; }
      el.style.transition = 'none';
      el.style.transform  = 'translateX(' + dx + 'px)'; // いったん前の位置に見せる
    });
    bar.offsetWidth; // ここで一度描き直させる(ないと動きが飛ぶ)
    items.forEach(function (el) {
      if (!el.style.transform) { return; }
      el.style.transition = 'transform ' + speed + 'ms ease';
      el.style.transform  = ''; // 今の位置へすべる
      el.addEventListener('transitionend', function done() {
        el.style.transition = '';
        el.removeEventListener('transitionend', done);
      });
    });
  }

  $btns.on('click', function () {
    var btn     = this;
    var $btn    = $(this);
    var wasOpen = $btn.attr('aria-expanded') === 'true';
    var $close  = $btns.filter('[aria-expanded="true"]');

    animateBar(function () {
      $close.attr('aria-expanded', 'false');
      if (!wasOpen) {
        $btn.attr('aria-expanded', 'true');
        if (bar.firstElementChild !== btn) {
          bar.insertBefore(btn, bar.firstElementChild); // 押したメニューを一番左へ
        }
      }
    });

    $close.each(function () { closePanel($('#' + $(this).attr('aria-controls'))); });
    if (!wasOpen) { openPanel($('#' + $btn.attr('aria-controls'))); }
  });
})(window.jQuery);
</script>

※ JavaScript は HTML より後ろに置く。先に置くと、メニューのボタンがまだ無いので動かない。
※ WordPress で jQuery をすでに利用している場合は、1行目の jQuery の読み込みは不要。利用していない場合は、下の「WordPress で使う場合」の方法(functions.php で読み込む)にすると、jQuery も一緒に読み込まれる。

JavaScript が動かない環境では、サブメニューが最初から全部開いた状態で出る。リンクはそのまま使える。 また、端末で「視差効果を減らす」(動きを減らす)設定にしている場合は、動きなしで開け閉めする。

調整箇所

調整するときの変更箇所は次のとおり。

変えたいもの変更する場所
開閉・移動の速さJavaScript の SPEED(ミリ秒)
文字の大きさCSS の --hn-size
メニューの文字太さCSS の --hn-weight
開いているメニューの文字太さCSS の --hn-open-weight
マウスを乗せたときの色CSS の --hn-hover

WordPress で使う場合

サブメニューの中身を、カテゴリ・年別アーカイブ・タグの一覧に置き換える。HTML の3つの <div class="hn-panel"> を、テーマの header.php などで下記に差し替える。記事を書くと一覧は自動で増える。

<div class="hn-panel" id="hn-categories" hidden>
  <ul>
    <?php
    // カテゴリ一覧(記事数つき)。<li> の形で出力される
    wp_list_categories( array( 'title_li' => '', 'show_count' => true ) );
    ?>
  </ul>
</div>
<div class="hn-panel" id="hn-archives" hidden>
  <ul>
    <?php
    // 年別アーカイブ(記事数つき)
    wp_get_archives( array( 'type' => 'yearly', 'show_post_count' => true ) );
    ?>
  </ul>
</div>
<div class="hn-panel" id="hn-tags" hidden>
  <ul>
    <?php foreach ( get_tags( array( 'orderby' => 'name' ) ) as $hn_tag ) : // タグ一覧(名前順) ?>
      <li><a href="<?php echo esc_url( get_tag_link( $hn_tag ) ); ?>"><?php echo esc_html( $hn_tag->name ); ?></a></li>
    <?php endforeach; ?>
  </ul>
</div>

JavaScript は <script> の中身(/* ===== JavaScript ===== から最後の })(window.jQuery); まで)を、テーマの js/headnavi.js に保存し、functions.php で読み込む。

// メニューの JavaScript(js/headnavi.js)を読み込む。jQuery(WordPress 同梱)を先に読み込ませる
function mytheme_enqueue_headnavi() {
	wp_enqueue_script(
		'mytheme-headnavi',
		get_template_directory_uri() . '/js/headnavi.js',
		array( 'jquery' ), // jQuery を使う
		'1.0.0',
		true // </body> の直前で読み込む
	);
}
add_action( 'wp_enqueue_scripts', 'mytheme_enqueue_headnavi' );

※ WordPress の jQuery は $ が使えないので、JavaScript は (function ($) { … })(window.jQuery); で囲んでいる。
※ CSS と <noscript> は、テーマの CSS と header.php の <head> 内に置く。

ちなみに、サブメニューの開閉は slideDown/slideUp を使わず、「今の高さ → 目標の高さ」を毎回決めて動かしている。すばやく続けて押しても、サブメニューが途中の高さで止まらず、必ず開ききる・閉じきる。

表示環境によって動きの見え方が異なる場合があるため、本番に反映する前に確認してね。

NOTICES

  • 記事内容は実装させたものがほとんどですが自己責任で参考にしてください。