HTMLとCSSのみを使用してマルチレベル階層的な浮揚ナビゲーションメニューを作成する


今日、私はあなたに複数のレベルの向こう側に入れ子にされることができる階層的なナビゲーションflyoutメニューを作成する方法に関する簡単なチュートリアルをするつもりです.
インスピレーションとして、デスクトップアプリケーションの例メニューバーの具体的な実用例で始める.これを説明するためにChromeブラウザのメニューバーのサブセットを選びます.
我々は、単純なかなりのルックアンドフィール、古典的なWindows™ テーマ.以下のような短いアニメーションを示します.

最後に、我々はそれをMacOSを与えるためにいくつかのよりスタイリングを追加することによって少しfancier作るよ™ 感じのように.
せっかち?下にスクロールして下にcodepen埋め込む!

基礎


我々のメニュー項目が一般に何を構成するかについて理解することによって始めましょう.次のプロパティが必要です.

  • ラベル:(必須)は、基本的に表示されるメニュー項目の名前です

  • target :(オプション)メニュー項目をクリックするためのレスポンスとしてユーザーをページに表示するハイパーリンク.我々は、たった今ちょうどリンクに固執します.より多くのダイナミックなページ機能の追加はJavaScriptを必要とします.それはあなたが常に行くことができると簡単に後で追加するものです.

  • ショートカット:(オプション)このメニュー項目に使用できるキーボードショートカットを表示します.例えば、"file > new "は"cmd + n "です.⌘n )

  • このメニュー項目のサブメニューを参照します.再帰的な構造の形で私たちのメニューとサブメニューを考えてください.視覚的に、サブメニューを有するメニュー項目はまた、それに矢印アイコンを有するべきである▶) ホバリング時に展開できることを示す.

  • 障害者:(optiona)、メニュー項目が相互に作用することができるかどうか示している状態.
  • 概念型パラメータ?(オプション)メニュー項目の種類をエミュレートできます.例えば、メニューのリストの中のいくつかのエントリは単に区切り記号として機能するはずです.
  • 我々は先に行くと私たちのメニューに複雑な行動を追加できることに注意してください.例えば、あるメニューはトグルアイテムであるかもしれません.したがって、いくつかの形式の目盛りを持っている必要があります✔) またはそれに関連付けられたチェックボックスがオン/オフ状態を示す.
    私たちはHTMLマークアップにCSSクラスを使用して、そのようなプロパティを示し、いくつかの賢いスタイリングを書き、すべての対応する振る舞いを与えます.

    HTMLの構造化


    以上に基づいて、基本メニューHTMLのように見えます.
  • メニューのリストはHTMLで定義されますul 要素、個々の項目を明らかにli .
  • ラベルとショートカットはspan 対応するCSSクラスを持つ要素label or shortcut ) アンカーの中a ) 内部タグli , それをクリックすると、ナビゲーションの動作を引き起こすだけでなく、いくつかのUIフィードバックを提供することができるなどのホバーのメニュー項目を強調表示します.
  • メニュー項目がサブメニュー(子供)のリストを含んでいるとき、我々はそのサブメニューを別のものに入れますul 現在のメニュー内の要素li 要素(親)など.この特定のメニュー項目にサブメニューが含まれていることを記述するために、いくつかの特定のスタイリングを追加することができます▶ を表示しますhas-children この親へのCSSクラスli .
  • セパレータのような項目についてはseparatorli それを示す項目.
  • メニュー項目を無効にすることができますdisabled CSSクラス.ホスティングやクリックのようなポインタイベントに反応しないようにすることです.
  • 私たちはコンテナの中にすべてをラップしますnav 要素(それがそうであることは良いです)semantic ) を追加し、flyout-nav クラスを追加します.
  • <nav class="flyout-nav">
        <ul>
            <li>
                <a href="#"><span class="label">File</span></a>
                <ul>
                    <li>
                        <a href="#">
                            <span class="label">New Tab</span>
                            <span class="shortcut">⌘T</span>
                        </a>
                    </li>
                    <li>
                        <a href="#">
                            <span class="label">New Window</span>
                            <span class="shortcut">⌘N</span>
                        </a>
                    </li>
                    <li class="separator"></li>
                    <li class="has-children">
                        <a href="#">
                            <span class="label">Share...</span>
                        </a>
                        <ul>
                            <li>
                                <a href="#">
                                    <span class="label">✉️ Email</span>
                                </a>
                            </li>
                            <li>
                                <a href="#">
                                    <span class="label">💬 Messages</span>
                                </a>
                            </li>
                        </ul>
                    </li>
                </ul>
            </li>
        </ul>
    </nav>
    

    CSSでの振る舞いの追加


    嘘をついた.使いましょうSCSS 代わりに.
    冗談脇、ここで面白い部分が来る!
    メニュー(最初のレベルを除いて“水平バー”)は、デフォルトで非表示にする必要があります.
    対応するメニュー項目がマウスポインタを使用しているとき、最初のレベルの下の何でも表示されるべきです.あなたが既に推測したかもしれないように、私たちはCSS hover pseudo-class これは.

    メニューとサブメニュー要素の配置
    おそらく、この全体のパズルの中で最も些細なビットは、どのようにサブメニューの位置を作成し、正しく自分の親のメニュー項目に関して自分自身を整列理解することです.これは、CSSのいくつかの知識ですpositioning 入る.それを見ましょう.
    我々がサブメニューを置くことを選んだ理由が、ありましたul 親の中の要素li 要素.もちろん、階層的な内容のマークアップを論理的に適切にまとめるのに役立ちますが、親要素の位置に対する子要素の位置を簡単に記述できるようにすることもできます.それから、我々はルートにずっとこの概念をとりますul and li 要素.
    これを行うために、我々はabsolute 位置決めとtop , left CSSプロパティは、最も近い非静的位置の祖先に対して子要素を配置するのに役立ちますcontaining block . 非静的では、要素のCSS位置プロパティがstatic ( HTMLドキュメントフローではデフォルトで起こります).relative , absolute , fixed or sticky . それを確かめるために、我々は位置を割り当てるつもりですrelativeli 子要素ul 位置する要素absolute .
    .flyout-nav {
        // list of menu items at any level
        ul {
            margin: 0;
            padding: 0;
            position: absolute;
            display: none;
            list-style-type: none;
        }
    
        // a menu item
        li {
            position: relative;
            display: block;
    
            // show the next level drop-down on
            // the right at the same height
            &:hover {
                & > ul {
                    display: block;
                    top: 0;
                    left: 100%;
                }
            }
        }
    
    この効果は、下の画像に表示されます.ビジュアルスタイリングのためのいくつかの追加のCSSは、それがすべて素敵に見えるようにイメージで行われているが、コアの動作は、我々が上記したものによって定義されます.これは深いレベル(実用性の限界内)に大きな働き続けます.

    これには1つの例外がありますが、これはメニュー項目の最初のレベルリスト(ファイル、編集、ビュー...)で、その子メニュー項目は右ではなく下に配置する必要があります.これを処理するには、以前のCSSにいくつかのスタイルを追加します.
    .flyout-nav {
        // ... other stuff
    
        // overrides for first-level behaviour (horizontal bar)
        & > ul {
            display: flex;
            flex-flow: row nowrap;
            justify-content: flex-start;
            align-items: stretch;
    
            // first-level drop-down should appear
            // below at the same left position
            & > li:hover > ul {
                top: 100%;
                left: 0;
            }
        }
    }
    
    ここでフレックスボックスを使用することは必須ではありませんでした.あなたは他のアプローチを使用して同様の行動を達成することができますdisplay: block and display: inline-blockul and li アイテムも.

    研磨
    メニューアイテムの配置の基本を処理したら、フォント、サイズ、色、背景、影などの追加のスタイルを書くことについて書きます.
    一貫性と再利用のために、SCSS変数の束を使用してこのような値を定義し、共有していると仮定しましょう.…
    // variables
    $page-bg: #607d8b;
    $base-font-size: 16px; // becomes 1rem
    $menu-silver: #eee;
    $menu-border: #dedede;
    $menu-focused: #1e88e5;
    $menu-separator: #ccc;
    $menu-text-color: #333;
    $menu-shortcut-color: #999;
    $menu-focused-text-color: #fff;
    $menu-text-color-disabled: #999;
    $menu-border-width: 1px;
    $menu-shadow: 2px 2px 3px -3px $menu-text-color;
    $menu-content-padding: 0.5rem 1rem 0.5rem 1.75rem;
    $menu-border-radius: 0.5rem;
    $menu-top-padding: 0.25rem;
    
    私たちは適切なスタイルと行動を追加して残っているいくつかの作品があります.我々は、すぐに彼らの上に行きます.

    アンカー、ラベル、ショートカット-実際の視覚的な要素
    .flyout-nav {
        // ... other stuff
    
        li {
            // ... other stuff
    
            // the menu items - text, shortcut info and hover effect (blue bg)
            a {
                text-decoration: none;
                color: $menu-text-color;
                position: relative;
                display: table;
                width: 100%;
    
                .label,
                .shortcut {
                    display: table-cell;
                    padding: $menu-content-padding;
                }
    
                .shortcut {
                    text-align: right;
                    color: $menu-shortcut-color;
                }
    
                label {
                    cursor: pointer;
                }
    
                // for menu items that are toggles
                input[type='checkbox'] {
                    display: none;
                }
    
                input[type='checkbox']:checked + .label {
                    &::before {
                        content: '✔️';
                        position: absolute;
                        top: 0;
                        left: 0.25rem;
                        padding: 0.25rem;
                    }
                }
    
                &:hover {
                    background: $menu-focused;
                    .label,
                    .shortcut {
                        color: $menu-focused-text-color;
                    }
                }
            }
        }
    }
    
    このコードのほとんどはかなり自明です.しかし、何か面白いことに気づいた?約input[type='checkbox'] ?

    アイテムを切り替える
    トグルでは、隠しHTMLを使用しますcheckbox 要素を維持する要素(オンまたはオフ)とスタイルlabel with ::before pseudo-element したがって.簡単なCSSを使うことができますadjacent sibling selector .
    そのメニュー項目に対応するHTMLマークアップは次のようになります.
    <li>
        <a href="#">
            <input type="checkbox" id="alwaysShowBookmarksBar" checked="true" />
            <label class="label" for="alwaysShowBookmarksBar">Always Show Bookmarks Bar</label>
            <span class="shortcut">⇧⌘B</span>
        </a>
    </li>
    

    セパレータ
    .flyout-nav {
        // ... other stuff
    
        li {
            // ... other stuff
    
            // the separator item
            &.separator {
                margin-bottom: $menu-top-padding;
                border-bottom: $menu-border-width solid $menu-separator;
                padding-bottom: $menu-top-padding;
            }
        }
    }
    

    障害者
    .flyout-nav {
        // ... other stuff
    
        li {
            // ... other stuff
    
            // don't let disabled options respond to hover
            // or click and color them different
            &.disabled {
                .label,
                .shortcut {
                    color: $menu-text-color-disabled;
                }
                pointer-events: none;
            }
        }
    }
    
    CSSpointer-events ここで実際のトリックを行います.設定none それは任意のポインタイベントのターゲットとして目に見えないようになります.

    すべてを一緒に置く


    ビルディングブロックの理解を得た今、一緒にまとめましょう.ここでアクションで我々のマルチレベル階層的な浮揚ナビゲーションメニューにcodepenリンクです!

    ファンシーのテーマ
    あなたがレトロなWindowsルックのファンでないならば、ここでは、それを見て、MacOSのような感じをするために、CSSに若干のマイナーな調整で同じコードのもう一つのバージョンがあります.

    何がうまくいかないのですか。


    私たちが扱っていないことがいくつかあります.始めに
  • あなたがそれについてNitpickyであるならば、ふるまいの大部分が大きく働く間、意図的なCSS唯一のアプローチの制限は現実のWindowsとMacOSアプリケーションメニューと違って、我々のメニューがすぐにすぐに、ポインタが外へ出るのを隠します.より快適な使用のために、一般的に我々がしたいことは、隠す前にクリックを待つことです.
  • どのような場合は、メニュー内の項目のリストが超長いですか?例としてブックマークリストを想像してください.いくつかの時点で、ビューポートの高さのパーセンテージで、スクロール可能なビューにキャップする必要があるかもしれません.日の終わりに、それは本当にあなたが構築しているユーザーエクスペリエンスの選択ですが、私は同様にそこに配置したいもの.
  • これが役に立つと思います.
    乾杯!