0
0

メニュー開閉メモ

Last updated at Posted at 2024-09-09

はじめに

メニュー開閉について対応することがあり、chatgpt先生に確認し見直しました。
こういう確認がすぐに動くコードで確認できるのはありがたいことです。
速習メモになりますが記載させて頂きます。

メニュー開閉

やりたいこと

以下のような表示で対応し、初期は大項目2と大項目5で項目まで表示されている
大項目クリック後、その項目の表が表示、非表示になる
大項目1
-項目1-1
-項目1-2
大項目2
-項目2-1
-項目2-2
大項目3
-項目3-1
-項目3-2
大項目4
-項目4-1
-項目4-2
大項目5
-項目5-1
-項目5-2

以下のように「大項目1」「大項目2」などの大項目をクリックすると、それぞれの項目(項目1-1、項目1-2など)が表示・非表示になるようなコードを作成しました。初期状態では、「大項目2」と「大項目5」の項目が表示される設定にしています。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>メニュー制御例</title>
    <style>
        .submenu {
            display: none;
            margin-left: 20px;
        }
        .submenu.active {
            display: block;
        }
    </style>
</head>
<body>
    <h1>メニュー制御例</h1>
    <ul id="menu">
        <li><a href="#" data-target="submenu1">大項目1</a>
            <ul id="submenu1" class="submenu">
                <li>項目1-1</li>
                <li>項目1-2</li>
            </ul>
        </li>
        <li><a href="#" data-target="submenu2">大項目2</a>
            <ul id="submenu2" class="submenu active">
                <li>項目2-1</li>
                <li>項目2-2</li>
            </ul>
        </li>
        <li><a href="#" data-target="submenu3">大項目3</a>
            <ul id="submenu3" class="submenu">
                <li>項目3-1</li>
                <li>項目3-2</li>
            </ul>
        </li>
        <li><a href="#" data-target="submenu4">大項目4</a>
            <ul id="submenu4" class="submenu">
                <li>項目4-1</li>
                <li>項目4-2</li>
            </ul>
        </li>
        <li><a href="#" data-target="submenu5">大項目5</a>
            <ul id="submenu5" class="submenu active">
                <li>項目5-1</li>
                <li>項目5-2</li>
            </ul>
        </li>
    </ul>

    <script src="script.js"></script>
</body>
</html>

CSS

.submenu {
    display: none;
    margin-left: 20px;
}

.submenu.active {
    display: block;
}

JavaScript

document.addEventListener('DOMContentLoaded', function () {
    const menuItems = document.querySelectorAll('#menu a');
    
    menuItems.forEach(item => {
        item.addEventListener('click', function (e) {
            e.preventDefault();
            
            const targetId = this.getAttribute('data-target');
            const targetMenu = document.getElementById(targetId);

            // 該当のサブメニューを表示・非表示を切り替える
            if (targetMenu.classList.contains('active')) {
                targetMenu.classList.remove('active');
            } else {
                targetMenu.classList.add('active');
            }
        });
    });
});

説明

  1. HTML: 各「大項目」のサブメニュー(項目1-1、項目1-2など)がsubmenu1submenu2のように定義されています。初期状態では「大項目2」と「大項目5」のサブメニューにactiveクラスが付いており、これらが表示されます。
  2. CSS: .submenuクラスにより、サブメニューは非表示 (display: none) がデフォルトです。activeクラスが付与されると表示されます (display: block)。
  3. JavaScript:
    • 各「大項目」をクリックすると、対応するサブメニューのactiveクラスがトグルされ、表示・非表示が切り替わります。

このコードにより、初期状態では「大項目2」と「大項目5」のみが展開されており、その他の大項目はクリックすることでサブメニューが表示・非表示されます。

js説明

JavaScriptの記述に関する詳細な説明を以下に示します。このコードでは、メニュー項目のクリックによってサブメニューの表示・非表示を制御しています。

JavaScriptコードの内容

document.addEventListener('DOMContentLoaded', function () {
    const menuItems = document.querySelectorAll('#menu a');
    
    menuItems.forEach(item => {
        item.addEventListener('click', function (e) {
            e.preventDefault();
            
            const targetId = this.getAttribute('data-target');
            const targetMenu = document.getElementById(targetId);

            // 該当のサブメニューを表示・非表示を切り替える
            if (targetMenu.classList.contains('active')) {
                targetMenu.classList.remove('active');
            } else {
                targetMenu.classList.add('active');
            }
        });
    });
});

各部分の説明

1. document.addEventListener('DOMContentLoaded', function () { ... });

この部分は、HTMLのページが完全に読み込まれた時点でJavaScriptのコードを実行するためのイベントリスナーです。DOMContentLoadedイベントは、ページのDOM(Document Object Model)が完全に準備されたことを知らせます。これにより、スクリプトがHTML要素を操作できるようになります。

2. const menuItems = document.querySelectorAll('#menu a');

ここでは、メニューのリンク要素(<a>タグ)を全て取得しています。document.querySelectorAllメソッドは、指定したCSSセレクタに一致する全ての要素を取得します。'#menu a'というセレクタは、メニュー(<ul id="menu">)内の全てのリンクを対象にしています。

  • menuItemsは、クリックイベントを設定する対象となるリンク要素を格納する変数です。

3. menuItems.forEach(item => { ... });

forEachメソッドは、配列やリストの各要素に対して繰り返し処理を行うためのメソッドです。ここでは、menuItemsに含まれる全てのリンク(<a>タグ)に対して、クリックイベントを追加する処理を行っています。

4. item.addEventListener('click', function (e) { ... });

この部分は、各リンク要素にクリックイベントリスナーを追加しています。addEventListenerメソッドは、指定されたイベント(今回はclick)が発生した時に、特定の関数(イベントハンドラ)が実行されるように設定します。

  • e.preventDefault();: この行は、リンクのデフォルトの動作をキャンセルするためのものです。通常、リンクはクリックすると別のページに遷移しますが、この動作を防ぎ、サブメニューの表示・非表示だけを制御します。

5. const targetId = this.getAttribute('data-target');

この行では、クリックされたリンク要素(this)のdata-target属性の値を取得しています。data-target属性には、表示・非表示を制御するサブメニューのIDが指定されています。

例えば、大項目1のリンクにはdata-target="submenu1"が設定されているため、この行によってsubmenu1というIDが取得されます。

6. const targetMenu = document.getElementById(targetId);

ここでは、取得したdata-targetの値に基づいて、対応するサブメニューのDOM要素を取得しています。document.getElementById(targetId)は、指定されたID(ここでは、例えばsubmenu1)を持つ要素を取得します。

7. if (targetMenu.classList.contains('active')) { ... }

この行では、クリックされたリンクに対応するサブメニューにactiveクラスが既に付いているかどうかをチェックしています。

  • targetMenu.classList.contains('active'): このメソッドは、サブメニューがactiveクラスを持っているかどうかを確認します。activeクラスが付いていれば、そのサブメニューは表示されていることを意味します。

8. targetMenu.classList.remove('active');

この行では、もしサブメニューにactiveクラスが付いている場合(既に表示されている場合)、activeクラスを削除して非表示にします。

9. targetMenu.classList.add('active');

逆に、サブメニューにactiveクラスが付いていない場合(非表示状態の場合)、この行でactiveクラスを追加し、サブメニューを表示します。


結果として

  1. 初期状態: HTMLでactiveクラスを持つサブメニュー(「大項目2」と「大項目5」のサブメニュー)は初期表示されます。
  2. クリック時の動作: ユーザーが「大項目」をクリックすると、その項目に対応するサブメニューが表示・非表示に切り替わります。

子要素として対応させる例

以下のコード例では、初期状態で「大項目2」と「大項目5」のサブメニュー(項目)が表示されている状態を作り、それ以外の項目は非表示にしています。クリックすると、対応する大項目のサブメニューが表示・非表示されます。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>メニュー開閉例</title>
    <style>
        /* メニューの親要素に対するスタイル */
        .menu-parent {
            margin-bottom: 10px;
            padding: 10px;
            background-color: #f0f0f0;
            cursor: pointer;
            border: 1px solid #ccc;
        }

        /* サブメニューを非表示にするためのスタイル */
        .submenu {
            display: none;
            margin-left: 20px;
            background-color: #e0e0e0;
            padding: 10px;
        }

        /* サブメニューの開いた状態 */
        .submenu.active {
            display: block;
        }

        /* ::after 擬似要素を使って、フロート解除 */
        .menu-parent:after {
            content: '';
            display: block;
            clear: both;
            height: 0;
        }
    </style>
</head>
<body>

    <!-- メニュー項目 -->
    <div class="menu-parent" data-target="submenu1">大項目1</div>
    <div id="submenu1" class="submenu">
        <p>項目1-1</p>
        <p>項目1-2</p>
    </div>

    <div class="menu-parent" data-target="submenu2">大項目2</div>
    <div id="submenu2" class="submenu active">
        <p>項目2-1</p>
        <p>項目2-2</p>
    </div>

    <div class="menu-parent" data-target="submenu3">大項目3</div>
    <div id="submenu3" class="submenu">
        <p>項目3-1</p>
        <p>項目3-2</p>
    </div>

    <div class="menu-parent" data-target="submenu4">大項目4</div>
    <div id="submenu4" class="submenu">
        <p>項目4-1</p>
        <p>項目4-2</p>
    </div>

    <div class="menu-parent" data-target="submenu5">大項目5</div>
    <div id="submenu5" class="submenu active">
        <p>項目5-1</p>
        <p>項目5-2</p>
    </div>

    <script src="script.js"></script>
</body>
</html>

JavaScript

document.addEventListener('DOMContentLoaded', function () {
    // 全てのメニュー要素を取得
    const menuItems = document.querySelectorAll('.menu-parent');

    menuItems.forEach(item => {
        item.addEventListener('click', function () {
            // 対象のサブメニューを取得
            const targetId = this.getAttribute('data-target');
            const submenu = document.getElementById(targetId);

            // サブメニューの表示・非表示をトグルする
            submenu.classList.toggle('active');
        });
    });
});

説明

  1. HTML部分:

    • 各大項目は .menu-parent クラスを持つ <div> 要素です。この要素をクリックすると、対応するサブメニューが表示・非表示されます。
    • 各サブメニューは .submenu クラスを持っており、最初は非表示に設定されています(display: none)。
    • active クラスを追加することで、サブメニューが表示されます。初期状態では、「大項目2」と「大項目5」に active クラスが既に付いており、それらの項目だけが表示されています。
  2. CSS部分:

    • .submenu クラスによりサブメニューは非表示状態です。
    • .submenu.active が付いている場合に、サブメニューが表示される仕組みです(display: block;)。
  3. JavaScript部分:

    • ページが完全に読み込まれた後に、.menu-parent クラスを持つ全ての大項目にクリックイベントリスナーを追加します。
    • 大項目がクリックされると、その要素に設定された data-target 属性を使って、対応するサブメニューのID(例えば、submenu1)を取得します。
    • submenu.classList.toggle('active'); を使って、サブメニューの active クラスをトグル(追加・削除)します。これにより、クリックするたびにサブメニューが表示・非表示されます。

結果

  • 初期状態では、「大項目2」と「大項目5」のサブメニュー(項目2-1、項目2-2、項目5-1、項目5-2)が表示されています。
  • 他の大項目は非表示状態です。
  • 各大項目をクリックすると、対応するサブメニューが表示または非表示になります。

::after 擬似要素を使って、フロート解除

このCSSコードに含まれる擬似要素 ::after とフロート解除(clearfix)の仕組みについて、もう少し詳しく解説します。

擬似要素 ::after

擬似要素 ::after は、指定した要素の直後に仮想的な要素を挿入するために使用されます。これは実際にはDOMに存在しない要素ですが、CSSを適用するために使います。この場合、空のコンテンツを追加することで、特定のレイアウトやスタイル調整に役立てています。

clear: both によるフロート解除

CSSの clear プロパティは、フロートされた要素の影響を解除(クリア)するために使われます。

フロートの基本的な動作

  • フロートを使うと、要素が親要素の流れから外れて他の要素の左や右に寄せられます。フロートを使うと、親要素がフロートされた子要素の高さを認識しないことがあります。これは、フロート要素が親要素の通常のレイアウトから外れているためです。
  • その結果、親要素の高さが0になり、レイアウトが崩れてしまうことがあります。

clear: both の役割

  • clear: both; を使用すると、その要素は前にある左寄せ (float: left;) や右寄せ (float: right;) のフロートされた要素の次に配置されるようになります。これにより、親要素が正しくフロートされた子要素を囲むことができるようになります。
  • フロートされた要素の影響を受けずに要素が配置され、親要素の高さが正常に計算されるようになるのです。

コードの意味

.menu-parent:after {
    content: '';
    display: block;
    clear: both;
    height: 0;
}

このコードが実現しているのは、フロート解除のための「clearfix」テクニックです。

  1. content: '';:

    • 擬似要素 ::after は、要素の直後に仮想的な要素を挿入します。content: ''; は、この擬似要素に空のコンテンツを追加しています。実際には何も表示されませんが、この空の要素がフロート解除を行うために役立ちます。
  2. display: block;:

    • この擬似要素をブロック要素として扱います。ブロック要素は、その要素の前後に余白を作り、横幅全体を占める性質を持っています。これにより、親要素の高さが正しく計算されるようになります。
  3. clear: both;:

    • このプロパティは、擬似要素がフロートされた要素の影響を受けないようにするために設定されます。clear: both; は、左や右にフロートされた要素があっても、その影響を受けずに下に配置されることを意味します。
  4. height: 0;:

    • 擬似要素自体には高さを持たせず、ページのレイアウトに余分なスペースを取らないようにするための設定です。この擬似要素はフロート解除のためだけに存在するため、見た目に影響を与える必要がないからです。

このテクニックの目的

このテクニックは「clearfix」(クリアフィックス)と呼ばれ、以下の目的を達成します:

  • フロートを使ったレイアウトで、親要素がフロートされた子要素を正しく囲むようにする。
  • レイアウトの崩れを防ぎ、フロート要素が親要素を突き抜けてしまうことを避ける。

具体的には、フロートを使ったメニューやナビゲーションのレイアウトが親要素の高さに影響しないように、このコードで親要素が正しい高さを持つようにします。

clear: both;について

確かに clear: both; を使ったフロート解除(clearfix)は、比較的古い方法であり、特にCSS3以前の時代に頻繁に使用されていました。現代では、フロートを使ったレイアウトはあまり一般的ではなく、より新しいCSSのレイアウト技術が主流になってきています。

なぜ clear: both; が使われていたのか

フロートは、最初はテキストを画像の左右に回り込ませるための機能でしたが、CSSレイアウト技術が未成熟な時期には、フロートを使って複雑なレイアウトを構築する方法が広く採用されていました。フロートされた要素を親要素内で正しく扱うために、clear: both; を使ってフロート解除をする必要があったのです。

現代の代替方法

現在では、より簡単かつ強力なレイアウト方法がCSSに追加されています。これにより、フロートやclear: both; を使う必要はほとんどなくなっています。いくつかの代表的な方法を挙げると:

1. Flexbox

Flexboxは、コンテナの中のアイテムを柔軟に配置するためのレイアウトモジュールです。フロートを使うよりも簡単に、複雑なレイアウトを実現できます。親要素に display: flex; を設定することで、子要素をフロート解除なしに配置でき、親要素の高さも正しく調整されます。

.menu-parent {
    display: flex;
    flex-direction: column;
}

2. Grid Layout

CSS Gridは、2次元のレイアウトを作成するための非常に強力なツールです。列や行を自由に指定できるため、フロートやクリアを気にせず、グリッド状のレイアウトを構築できます。

.menu-parent {
    display: grid;
    grid-template-columns: 1fr;
}

3. Overflow: auto

フロートを使わなければならない場合でも、clear: both; を使う代わりに、親要素に overflow: auto; を指定することでフロート解除を簡単に行うことができます。これも一種のフロート解除のテクニックです。

.menu-parent {
    overflow: auto;
}

結論

clear: both; を使ったフロート解除は、古いブラウザやCSS2以前の仕様をサポートする必要がある場合には今でも使われることがありますが、現在のプロジェクトではFlexboxやGridなど、よりモダンなレイアウト技術を使うことが一般的です。これにより、レイアウトをより簡単かつ柔軟に管理できるようになっています。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0