0
0

More than 1 year has passed since last update.

JavaScriptでドロップダウンを実装する

Posted at

はじめに

Javascript研修を通して、ドロップダウンメニューを実装する。

コーディングルール

  • ES6 (EcmaScript2015)以降のモダンな記法を使う
  • 関数をモジュール化して使用する
  • data 属性を使用してDOM要素を取得する

目指す動作

Something went wrong

コード(pug, scss)

すでにpugファイルとscssファイルは用意されており、JSファイルを作成する。
この2つのファイルはすでに用意されているもののため割愛。

_dropdown.pug
.dropdown
  .dropdown__inner
    nav
      ul.dropdown__menu
        li.dropdown__menuItem(data-selector="dropdown-menu-item") ABOUT
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem about 1
        li.dropdown__menuItem(data-selector="dropdown-menu-item") SERVICE
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem service 1
            li.dropdown__subMenuItem service 2
        li.dropdown__menuItem(data-selector="dropdown-menu-item") NEWS
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem news 1
            li.dropdown__subMenuItem news 2
            li.dropdown__subMenuItem news 3
        li.dropdown__menuItem(data-selector="dropdown-menu-item") CONTACT
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem news 1
            li.dropdown__subMenuItem news 2
            li.dropdown__subMenuItem news 3
            li.dropdown__subMenuItem news 4
_dropdown.scss
@use 'modules/vars';

.dropdown {
  display: flex;
  justify-content: center;
  padding-bottom: 216px;
  margin-top: 128px;

  &__menu {
    display: flex;
    background-color: vars.$themeWhite;
  }

  &__menuItem {
    position: relative;
    width: 140px;
    padding: 16px 0;
    font-family: 'League Gothic', sans-serif;
    font-size: 24px;
    text-align: center;
    letter-spacing: 0.1em;

    &:hover {
      color: vars.$themeWhite;
      background-color: vars.$themeBlack;
    }
  }

  &__subMenu {
    position: absolute;
    display: none;
    width: 140px;
    margin-top: 16px;
    background-color: vars.$themeBlack;

    &.--active {
      display: block;
    }
  }

  &__subMenuItem {
    padding: 16px 0;
    font-size: 20px;
    letter-spacing: 0.1em;
    border: 1px solid vars.$themeBlack;

    &:hover {
      color: vars.$themeBlack;
      background-color: vars.$themeWhite;
    }
  }
}

JSコード解説

全文は最後に記述しています。
まず1つずつ実際のコードの解説をしていきます。

変数の定義

まずは、ドロップダウンメニューの操作に必要な変数を定義しています。

const dropdownMenuItem = '[data-selector="dropdown-menu-item"]';
const dropdownSubMenu = '[data-selector="dropdown-sub-menu"]';
const ACTIVE = '--active';

ここでは3つの変数を定義しています。

  • dropdownMenuItem:ドロップダウンメニューの項目を特定するためのセレクタ。
  • dropdownSubMenu:サブメニューを特定するためのセレクタ。
  • ACTIVE:サブメニューが表示されている状態を示すCSSクラス名。

上書きされる可能性の低い'ACTIVE'のみ大文字で記述しております。
他の定数はキャメルケースで書いています。
このあたり正解がわからないので、もしアドバイス等あれば教えてください。

イベントハンドラの定義

次に、メニュー項目にマウスが乗ったときや外れたときの動作を定義します。

const showSubMenu = (event) => {
  const subMenu = event.target.querySelector(dropdownSubMenu);
  subMenu.classList.add(ACTIVE);
};

const hideSubMenu = (event) => {
  const subMenu = event.target.querySelector(dropdownSubMenu);
  subMenu.classList.remove(ACTIVE);
};

showSubMenu関数は、メニュー項目にマウスが乗ったときにサブメニューを表示するための関数です。

event.target

event オブジェクトには、イベントが発生した要素情報が含まれています。
event.target で、マウスが乗ったメニュー項目の要素を取得します。

querySelector(dropdownSubMenu)

querySelectorメソッドを使って、マウスが乗ったメニュー項目内のサブメニュー要素を取得します。

classList.add(ACTIVE)

取得したサブメニュー要素に --activeクラスを追加します。
このクラスが付与されることで、CSSで定義されたスタイルによりサブメニューが表示されるようになります。

対照的に、hideSubMenu関数はサブメニューを非表示にするための関数です。

event.targetquerySelector(dropdownSubMenu)

showSubMenu と同様に、イベントが発生したメニュー項目要素と、その中のサブメニュー要素を取得します。

classList.remove(ACTIVE)

取得したサブメニュー要素から --active クラスを削除します。
このクラスが削除されることで、サブメニューが非表示になります。

イベントリスナーの追加

最後に、上記で定義したイベントハンドラをドロップダウンメニューの項目に追加しています。

menuItems.forEach((item) => {
  item.addEventListener('mouseenter', showSubMenu);
  item.addEventListener('mouseleave', hideSubMenu);
});

ここでは、全てのメニュー項目に対して、マウスが乗ったときは showSubMenu を実行し、マウスが外れたときは hideSubMenu を実行するようにしています。

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

menuItems は、ドロップダウンメニューの項目をすべて含むDOM要素のコレクションです。
forEach は、このNodeList内の各要素に対して、指定された関数を実行するメソッドであり、
すべてのメニュー項目に対して何らかの操作を行うためのものです。(以下動作)

item.addEventListener('mouseenter', showSubMenu)

メニュー項目にマウスが乗ったときに発火するイベントリスナーを追加しています。
mouseenter は、要素の上にマウスが移動したときに発火するイベントです。
このイベントが発生すると、showSubMenu 関数が実行され、サブメニューが表示されます。

item.addEventListener('mouseleave', hideSubMenu)

この行は、メニュー項目からマウスが離れたときに発火するイベントリスナーを追加しています。
mouseleave は、要素からマウスが離れたときに発火するイベントです。
このイベントが発生すると、hideSubMenu 関数が実行され、サブメニューが非表示になります。

モジュール化処理を実行し、index.jsでimportする

最終行で下記コマンドを打つことで、モジュール化したJSを外部からimportできるようになり、
それをindex.js でimportしてあげます。

export default dropdown;
import dropdown from './modules/dropdown';

dropdown();

最終的なJSコード

dropdown.js
/**
 * ドロップダウンメニューを操作する関数
 * @function dropdown
 */
const dropdown = () => {
  /**
   * 'dropdown-menu-item'のdata-selector属性を指定する文字列
   * @type {string}
   */
  const dropdownMenuItem = '[data-selector="dropdown-menu-item"]';

  /**
   * 'dropdown-sub-menu'のdata-selector属性を指定する文字列
   * @type {string}
   */
  const dropdownSubMenu = '[data-selector="dropdown-sub-menu"]';

  /**
   * サブメニューの表示状態を制御するCSSクラス名
   * @type {string}
   */
  const ACTIVE = '--active';

  /**
   * 'dropdown-menu-item'のdata-selector属性を持つすべてのDOM要素を取得
   * @type {NodeList}
   */
  const menuItems = document.querySelectorAll(dropdownMenuItem);

  /**
   * 指定したメニュー項目のサブメニューを表示
   * @param {Event} event - マウスを置いたときのイベント
   */
  const showSubMenu = (event) => {
    const subMenu = event.target.querySelector(dropdownSubMenu);
    subMenu.classList.add(ACTIVE);
  };

  /**
   * 指定したメニュー項目のサブメニューを非表示にする
   * @param {Event} event - マウスを外したときのイベント
   */
  const hideSubMenu = (event) => {
    const subMenu = event.target.querySelector(dropdownSubMenu);
    subMenu.classList.remove(ACTIVE);
  };

  menuItems.forEach((item) => {
    item.addEventListener('mouseenter', showSubMenu);
    item.addEventListener('mouseleave', hideSubMenu);
  });
};

export default dropdown;

index.js
import dropdown from './modules/dropdown';

dropdown();

まとめ

初めてJavaScriptの機能をしっかりと学んで実装したが、JSDocを書いてあげることで型を宣言することができたり、個人的に要素の意味を理解しやすかったりした。
定数に関して、constは変数と違い上書き不可だと思っていたが、実は絶対ではないとのこと。使い分けがまだ難しい。。慣れるしかない。

0
0
0

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