見出し画像

detailsでカンタン!ハンバーガーメニュー

ハンバーガーメニュー。
それは、多くのWebサイトで当たり前のように使われているUIでありながら、未だ専用のHTMLが用意されていない難しい存在。

初期はJavaScriptを使用して出し入れをしていましたが、その後checkboxを使って出し入れをするというトリッキーな技が発明され、ある程度広まりました。
しかし、checkboxの本来の用途とは異なる使い方ですし、CSSが外れるとcheckboxが丸見えになる上に、本来のハンバーガーメニュー部分ではなく、checkbox部分を操作しなければメニューの開閉が出来ないなど、問題も多い方法です。

そうだ、detailsだ!

そこで現状「最もハンバーガーメニューに相応しいタグ」が、detailsです。
detailsは、通常は要約された一部のみを表示しておき、要約部分をクリックすると隠れていた詳細部分が表示されるというギミックを持った要素です。
JavaScriptなしで動作し、書き方も簡単なのでおすすめです。

detailsの書き方

<details>
<summary><span>MENU</span></summary>
<nav>
<ul>
<li>メニューA</li>
<li>メニューB</li>
<li>メニューC</li>
<li>メニューD</li>
</ul>
</nav>
</details>

detailsの書き方はとても簡単です。
<details>で囲った中に、<summary>タグで常時表示しておきたい要約部分を書きます。
今回はハンバーガーメニューボタンの部分になりますので、MENUを書いてあります。(<span>で囲んである理由は後程出てきます)

<summary>に続けて、出し入れする部分の内容を書きます。
今回はメニュー項目になりますので、<nav>や<ul>でそれっぽく作ります。

CSS未設定のdetails


CSS未設定のdetailsオープン時

CSS未設定だと、このような表示になります。
この時点で、「▶MENU」の部分を押せば、メニュー内容が表示され、再度押すとメニュー部分が閉じます。

メニュー部分を作る

考え方としては、<details>全体がメニューになり、<summary>のみを<details>から独立させて任意の位置に表示させる感じです。
まずはメニュー部分の出し入れを工夫してみましょう。

details {
    position:fixed;
    top:0;
    right:-100vw;
    width:100vw;
    height:100vh;
    background:#cef;
    transition:0.5s;
}

画面いっぱいに右からスライドしてくるメニューです。
right:-100vwで、画面右外に配置しています。
transitionは、スライドしてくる秒数です。0.5sなので0.5秒です。

details[open] {
    right:0;
}

details[open]で、detailsが開いているときの状態を指定できます。
今回はメニューのright位置を0にしています。

この状態では、detailsの中にあるsummaryも画面外に飛ばされてしまい押せないので、次にsummaryのCSSを指定します。

summary {
    position:fixed;
    width:40px;
    height:40px;
    padding:10px;
    background:#ddd;
    border-radius: 4px;
    top:20px;
    right:20px;
    cursor: pointer;
    list-style: none;

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

positionで位置を固定し、画面右上に表示されるよう設定しています。
また、ボタンのサイズを40pxの正方形にして、それっぽくしています。
list-style:none;は、「▶」表示を消すためのものです。
cursor:pointer;で、カーソル形状を変えると、PCでも押せる感が出ます。

最後の3行は、ハンバーガーマークの表示位置を調整するための指定です。
今回は「MENU」という文字がボタンの下側に表示されるので、
align-items: flex-end;を指定しています。

この時点でパティのないハンバーガーメニューができています

ここまでで、ボタンを押すとメニューがスライドイン・アウトするハンバーガーメニューが出来上がっていると思います。
続いて、ハンバーガーらしい横線を作成しましょう。

summary span {
    font-size:0.8em;
    line-height:1;
}
summary span::before ,
summary span::after {
    content: '';
    left: 10px;
    width: 40px;
    height: 3px;
    position: absolute;
    background: #000;
    border-radius: 2px;
    transition: 0.4s;
}
summary span::before {
    top: 15px;
}
summary span::after {
    top: 25px;
}

<summary>の中に書いていた<span>に対してCSSを適用していきます。
<span>の疑似要素を使うことで、線を作成しています。
線の幅はボタンの幅と同じ40pxですが、ボタンには10pxのpaddingがあるため、線の表示位置はleft:10pxとしています。
線の高さは3pxにしています。任意の高さにしてみましょう。
transitionは、ボタンを押した際に線がアニメーションする時間です。

最後に、::before、::afterそれぞれの縦位置を指定しています。
お好みで決めてください。

これで、線が2本ついてハンバーガーメニューらしくなりました。
続いて、ボタンを押したときに2本線が✕に変わるようにします。

details[open] summary span::before {
    top: calc((40px / 2) - (3px / 2));
    transform: rotate(20deg);
}
details[open] summary span::after {
    top: calc((40px / 2) - (3px / 2));
    transform: rotate(-20deg);
}

details[open]によって、メニューが開いている場合の状態を指定しています。

top: calc((40px / 2) - (3px / 2));

という計算部分がありますね。
40px / 2 の部分は、線の幅÷2を表しています。
3px / 2 は、線の高さ÷2を表しています。
この計算で、2本の線が交わる位置を計算し、設定しています。
※ボタンの形状などによって計算式は変わります。

メニューを開いた状態完成

これで、メニューを開いたときは✕表示にアニメーションするようになりました!

CSS全体はこちらです

details {
    position:fixed;
    top:0;
    right:-100vw;
    width:100vw;
    height:100vh;
    background:#cef;
    transition:0.5s;
}
details[open] {
    right:0;
}
nav {
    padding:100px 20px 20px 20px;
}

summary {
    position:fixed;
    width:40px;
    height:40px;
    padding:10px;
    background:#ddd;
    border-radius: 4px;
    top:20px;
    right:20px;
    cursor: pointer;
    list-style: none;

    display:flex;
    align-items: flex-end;
    justify-content: center;
}
summary span {
    font-size:0.8em;
    line-height:1;
}
summary span::before ,
summary span::after {
    content: '';
    left: 10px;
    width: 40px;
    height: 3px;
    position: absolute;
    background: #000;
    border-radius: 2px;
    transition: 0.4s;
}
summary span::before {
    top: 15px;
}
summary span::after {
    top: 25px;
}
details[open] summary span::before {
    top: calc((40px / 2) - (3px / 2));
    transform: rotate(20deg);
}
details[open] summary span::after {
    top: calc((40px / 2) - (3px / 2));
    transform: rotate(-20deg);
}

JavaScriptが必要なこと

detailsを使えばHTMLとCSSだけでハンバーガーメニューができ、CSSが外れた状態でも開閉式メニューとしてきちんと機能するハンバーガーメニューが作れるので、checkbox方式に比べるとかなり良いと思います。
ただ、限度もあります。

メニューが収納されるときに内容が消える

メニューが出てくるときにはメニュー内容が表示されていますが、収納されるときにはスライドアウトする前にメニュー内容が消えてしまいます。
これはsummary部分を押した瞬間にdetailsの内容が表示・非表示されているためです。
これを回避するには、JavaScriptでメニュー収納時(detailsのopenが外れるタイミング)を制御する必要があります。
(0.4秒でスライドアウトするなら、0.4秒後にdetailsのopenを外す)

レスポンシブ対応

これはかなりクリティカルかもしれませんが、PCの時にはハンバーガーをやめて、スマホのときだけハンバーガー化する、というレスポンシブ対応が出来ません。
メニュー内容を表示するにはdetailsにopenを付けなければならないので、それをJavaScriptでコントロールする必要があります。

まったく正しくないけど裏技もある

レスポンシブ問題は、以下のようにすればJavaScriptなしでも対応できます。

<details>
<summary><span>MENU</span></summary>
</details>
<nav>
<ul>
<li>メニューA</li>
<li>メニューB</li>
<li>メニューC</li>
<li>メニューD</li>
</ul>
</nav>

なんということでしょう!
<nav>が<details>の外に出ています!
これによって<nav>は<details>の開閉の影響を受けることなく、メディアクエリなどで自由にレスポンシブ化できます。
ハンバーガーメニュー時の開閉の判定は

details[open] + nav

と書くことで可能です。

でも、おかしいのでお勧めはしません・・。
今は、JavaScriptを使わなければ動作すらしないタグもありますから、ここまでしてJavaScript不使用にこだわる理由はありません。

背景固定

メニューを開いているときには、背景のWebページはスクロールしないように固定したいですね。
これもJavaScriptによるclassの付け外しを行わなければいけません。

全部入りのCSSフレームワーク TRISITE CSS

JavaScriptを使わなければ出来ないようなこともすべてあらかじめ設定済みで、CSSとJSファイルを読み込むだけで華麗なハンバーガーメニューを実装できるTRISITE CSSというものがあります!
ハンバーガーメニュー部分のみだったら、nav.cssとnav.jsのみを利用できるのでお気軽に導入可能ですよ。

以上、detailsでカンタン!ハンバーガーメニューでした。