Multi Vitamin & Mineral

Multi Vitamin & Mineral です。プログラムに関することを書いております。

【TIPS】CSS のみで開閉するメニューを作る

f:id:hiranoon:20210409161544p:plain

モバイル用ページのヘッダーによくあるハンバーガーメニュー。 です。クリックするとメニューが表示するヤツです。この開閉を JavaScript を使わずに、CSS だけで実現したいと思いまして、その TIPS の紹介です。

JavaScript を使った方が簡単かも知れませんが、使いたくない場面もあろうかと。

結果

パターン1

「Result」の下にある (ハンバーガーメニュー)ボタンをクリックすると、メニューボタン下にぶら下がるようにメニューが開きます。メニューボタンは で切り替わるようにしています。

See the Pen mobile-sidemenu-button-03-design by hiranoon (@hiranoon) on CodePen.

パターン2

ほぼ同じではありますが、 ボタンをクリックすると、画面左半分にメニューが開くパターンも用意しました。メニュー以外の領域(右半分の半透明のグレーの部分)をクリックすると閉じます。

See the Pen mobile-sidemenu-area-03-design by hiranoon (@hiranoon) on CodePen.

仕組みと実装方法(パターン1)

まずはパターン1の実装方法を紹介し、その中で仕組を説明していきます。

見えないチェックボックスの利用

JavaScript を使わない仕組みのミソは、見えないチェックボックスです。チェックの ON/OFF を 開/閉 に利用します。

ベースの HTML・CSS

ベースとなる HTML・CSS は以下のようになります。

See the Pen mobile-sidemenu-button-01-base by hiranoon (@hiranoon) on CodePen.

チェックボックスは後ほど CSS で非表示にします。

openclose というラベルがあります。これはチェックボックスと連動します。(仮でこのような文言にしていますが、後ほど  に置き換えます。)

背景オレンジ色の部分がメニューです。ここはチェック ON で表示、チェック OFF で非表示になるようにします。

ベースの HTML・CSS
ベースの HTML・CSS

チェックボックスとボタンの連動

先程の HTML の時点で実現できていますが、チェックボックスとラベル(openclose)の連動は HTML のみで行います。

<input type="checkbox" id="check" />
<label for="check" id="open">open</label>
<label for="check" id="close">close</label>

チェックボックスの id (ここでは check とした)を、ラベルの for に書けば連動します。

チェック状態による表示・非表示

この状態の HTML に CSS のみで表示・非表示機能を組み込んだのが以下になります。

See the Pen mobile-sidemenu-button-02-action by hiranoon (@hiranoon) on CodePen.

一般兄弟結合子

CSS のセレクタをつなげるモノに「一般兄弟結合子」というものがあります。 ~ を使います。

例。

#target ~ p {
  color: red;
}

こう書いたら、

  • #target :
    • id="target" より
  • ~ :
    • 後ろにあり、
    • 同じ親要素を持つ、
  • p:
    • <p> タグは、
  • color: red; :
    • 赤文字になる。

という意味になります。

以下のような HTML があれば、 <div id="target">起点</div> より後ろが赤文字です。また、同じ親要素を持つ必要があります。前にあったり別階層は対象になりません。(ちなみに、兄弟(= 同一階層)の子は対象になります。)

<!-- ◯は赤文字。✕は赤文字じゃない。 -->
<div>
  <p>✕(前なので)</p>
  <div id="target">起点</div>
  <p>◯(兄弟は対象である)</p>
  <div>
    <p>◯(兄弟の子も対象になる)</p>
  </div>
</div>
<p>✕(別階層なので)</p>

後ろは対象で前は非対象なので、兄貴はダメで、弟はOKって考えればよいです。 CSS 的には「一般兄弟」は弟のことを指します。兄弟と言いつつ兄は要らんかったんです。

MDN のリンクも貼っておきます。

developer.mozilla.org

チェック状態と関連付ける

わかりやすいよう、一旦メニュー部分(リンク1、リンク2、リンク3の部分)を抜き出します。 CSS は以下のようになっています。

#menu {
  display: none; /* チェックがない場合は非表示 */
}
#check:checked ~ #menu {
  display: block; /* チェックがある場合は表示 */
}

HTML は以下。

  <input type="checkbox" id="check" />
  <!-- 中略 -->
  <ul id="menu">
    <li>リンク1</li>
    <li>リンク2</li>
    <li>リンク3</li>
  </ul>

CSS の2つ目に書いた #check:checked ~ #menu に注目してください。

  • #check:checked (前半部分)は、
    • チェックが付いている状態<input type="checkbox" id="check" /> を指します。(#check<input type="checkbox" id="check" /> で、 :checked はチェックが付いている状態を指す疑似セレクタ。)
  • #check:checked ~ #menu は、
    • チェックが付いた <input type="checkbox" id="check" />後ろにある <ul id="menu">...</ul> を指します。

つまり、「チェックが付いた」を実現するために …:checked を使い、「チェックが付いた AND その後ろ」を実現するために …:checked ~ … を使ったということです。これでチェック状態と後ろの要素が関連付きました

チェック状態と関連付けたい HTML 要素は <label ...>open</label><label ...>close</label><ul id="menu">...</ul> の3つあります。

いずれの HTML 要素もチェックボックスの後ろ置けば :checked ~ ... で指定できますので、「一般兄弟結合子」の ~ は都合が良かったのです。

チェック状態と関連付け
チェック状態と関連付け

2つのボタンと1つのメニューのそれぞれに対し、チェックがない場合とチェックがある場合の表示・非表示の設定を CSS で定義すれば OK です。

最後に、チェックボックス自身は #check { display: none; } で常に非表示にしておきます。

デザインの調整

あとは CSS をいじってデザインを調整するとよいかと思います。デザインは本題ではないですが、まあ、こんな感じになりますよという例として簡単に調整したものが以下です。(最初に貼り付けたものと同じです。)

See the Pen mobile-sidemenu-button-03-design by hiranoon (@hiranoon) on CodePen.

開閉のボタンの は CSS で書きました。 FontAwesome のようなウェブフォントを使ってもいいと思います。

実装方法(パターン2)

次に、画面左半分にメニューが開くパターンです。ちょっと CSS の書き方が違うだけで仕組みは同じです。

ベースの HTML・CSS

パターン2は以下に示すベースの CSS の書き方が肝です。それ以外はパターン1との違いはありません。

ということで、ベースは以下です。

See the Pen mobile-sidemenu-area-01-base by hiranoon (@hiranoon) on CodePen.

閉じるボタンの <label ... id="close"></label> と、メニューの <ul id="menu">...</ul> は、どちらも画面全体に表示させています。

#close {
  /* 画面左上に配置 */
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  /* 画面全体 */
  width: 100%;
  height: 100%;
  /* menu より下 */
  z-index: 99;
  /* ...中略... */
}
#menu {
  /* 画面左上に配置 */
  position: fixed;
  top: 0;
  left: 0;
  /* 画面左4割分 */
  width: 40%;
  height: 100%;
  /* close より上 */
  z-index: 100;
  /* ...中略... */
}
  • position: fixed; top: 0; left: 0; で HTMLタグを書いている位置は無視して画面の左上を基点とします。
  • widthheight をパーセント定義することで、画面全体に描画しています。
  • z-index を定義して、閉じるボタン( label )を下に、メニュー( ul )を上にします。(数字の 99 と 100 は適当です。1 と 2 でも OK です。)
  • 閉じるボタン( label )はインライン要素なので幅と高さを持ちません。 display: block; で無理やりブロック要素( divul などと同じ)扱いにし、幅と高さを持たせます。

パターン2の肝はこの閉じるボタンとメニューの配置です。それ以外はパターン1と同じです。

チェックボックスとボタンの連動

動きを付けたものが以下になります。

See the Pen mobile-sidemenu-area-02-action by hiranoon (@hiranoon) on CodePen.

これはパターン1と同じことを行っているだけですので説明は割愛します。

デザインの調整

パターン1と同様、ちょっとそれっぽくしました。デザインは本題ではありませんので参考までということで。

See the Pen mobile-sidemenu-area-03-design by hiranoon (@hiranoon) on CodePen.

あとがき

まあ正直、 JavaScript を使った実装の方が素直だとは思います。チェックボックスを連動させるため、 HTML 要素を兄弟となるようムリクリ配置した感もなきにしもあらず、ですし。

当ブログのモバイル用にメニューを追加したのですが、その際に JavaScript を追加したくなかったんですね。なので、この TIPS を利用しました。

ブログやネットショップのような既存のシステムに、あとから自作パーツを追加するとか、そのシステムが JavaScript の追加ができないとか、そういう場面で使えるでしょうか。

ああ、そうそう、 openclose って2つのボタンを用意しなくても OK です。説明の都合上2つにしましたが、ボタンは1つで中身を切り替えるような CSS にしても問題なし。です。