JSライブラリ

Micromodal.jsをグローバルナビで使う(背景固定含む)

復習や注意点を含めて備忘録に。
(* 以下では、ハンバーガーメニューのことをドロワーメニューと呼称しています。)

目次

実装例

開閉ボタン【 一括ver. 】

See the Pen G-nav_1button by Masayuki / ウェブ制作者 (@pazfinder) on CodePen.

実用性が高いのは ↑こちらかも。

以下を参考にしました。

開閉ボタン【 個別ver. 】

See the Pen G-nav_2button by Masayuki / ウェブ制作者 (@pazfinder) on CodePen.

こちらの方がコードが多くCSSが複雑でアレンジしにくいです…
ですが、
Gナビやモーダルポップアップなど複数のモーダルコンテンツを扱う場合は、対応が楽になります。
(背景固定などのオプションが共通の場合は、JSへの記述が MicroModal.init(); のみで済みます)

以下、説明です。

Micromodal.jsとは

アクセシビリティに配慮したモーダル機能を実装できるライブラリです。
アクセシビリティとは「利用者が機器・サービスを円滑に利用できること」を指しており、音声ガイドを利用している方や、キーボード操作のみでサイト閲覧している方へ配慮しています。
サイズも軽量のJavaScriptライブラリです。

このライブラリを今回グローバルナビとして使っています。

使い方

npmもしくは、yarnからインストール

npm install micromodal --save
yarn add micromodal --save

CDNの読み込み

https://unpkg.com/micromodal/dist/micromodal.min.js

GitHubからファイルのダウンロード

詳細は、公式をご覧ください。

Micromodalで作る利点

実装には少々手間がかかりますが、Micromodalを利用することで得られる利点を挙げてみます。

・アクセシビリティに配慮されている。
これは前述した通りです。キーボードの操作はTabキーで項目を移動し、ESCキーでモーダルを閉じることが出来ます。

・ドロワーメニュー表示の際は背景固定が可能。
メニュー表示の際に背景がスクロール出来てしまうと使用者の混乱を招きやすいです。(意図したデザインの場合を除く)
表示中は背景を固定することでコンテンツにのみ意識が向けられるよう配慮することが出来ます。

・どこでもクリックでメニューを閉じることが可能。
ドロワーメニューを閉じる際に、閉じるボタンまで親指を伸ばす…またはカーソルを移動する必要がなくなります。メニュー項目以外が閉じる役割を担っているので、操作性の良いドロワーメニューを制作することが出来ます。

開閉ボタン【 一括ver. 】の説明

HTML

<header class="header">

      <div class="header__inner">

        <nav class="nav" aria-label="ヘッダー内メニュー">
          <!-- ドロワー -->
          <div id="drawer-1" class="nav__drawer" aria-hidden="true">

            <!-- 開閉ボタン -->
            <button id="js_drawer_button_open" class="nav__drawer-button" aria-label="メニューを開閉する" data-micromodal-trigger="drawer-1" aria-expanded="false" type="button">
              <span class="button-line"></span>
            </button>

            <!-- 幕 -->
            <div class="nav__drawer-overlay" data-micromodal-close tabindex="-1"></div>

            <!-- メニューコンテンツ -->
            <div class="nav__drawer-container" data-micromodal-close aria-modal="true" aria-labelledby="drawer-1-content" role="dialog">

              <div class="nav__drawer-menu">
                <ul id="drawer-1-content" class="nav__drawer-menu-list">
                  <li><a href="">テスト1</a></li>
                  <li><a href="">テスト2</a></li>
                  <li><a href="">テスト3</a></li>
                  <li><a href="">テスト4</a></li>
                  <li><a href="">テスト5</a></li>
                </ul>

              </div>
            </div>

          </div>
        </nav>

      </div>
    </header>

公式の使い方では、
[aria-hidden="true"]がある [class]に [is-open]の付け外しで、その要素を表示・非表示としてますが、
紹介する方法では、
18行目のメニューコンテンツを包容している[class="nav__drawer-container"]に対して、表示・非表示をおこなっています。

共通のCSSその1(リセットCSS)

/* リセットCSS ------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

button {
  all: unset;
}

body {
  background: #fff;
}

共通のCSSその2(キーボード操作用)

/* アクセシビリティ用 ------------------------------- */

/* マウスクリック操作の場合は ouline を消し、キーボード操作の場合は outline を表示する場合 */
:focus:not(:focus-visible) {
  outline: 0;
}

/* アウトライン表示 */
:focus-visible {
  outline: 2px solid red;
  outline-offset: 0;
}

キーボード操作時のアクセシビリティ対策です。

以下、参考元です

共通のCSSその3(メニュー表示・非表示)

/* -------------------------------------------------------------------- */
/*
/* メニュー表示・非表示
/* -------------------------------------------------------------------- */

  /* クリック・メニュー表示 ------------------------------- */
  .nav__drawer[aria-hidden="false"] {

    .nav__drawer-menu {
      animation: slideIn 0.5s forwards;
    }
  }

  /* クリック・メニュー非表示 ------------------------------- */
  .nav__drawer[aria-hidden="true"] {

    .nav__drawer-menu {
      animation: slideOut 0.5s forwards;
    }
  }

  /* キーフレーム slideIn ------------------------------- */
  @keyframes slideIn {
    0% {
      transform: translateX(100%);
    }

    100% {
      transform: translateX(0%);
    }
  }

  @keyframes slideOut {
    0% {
      transform: translateX(0%);
    }

    100% {
      transform: translateX(100%);
    }
  }

HTMLの.nav__drawerにある[aria-hidden="true または false"]を切り替えることによって、メニュー内容の.nav__drawer-menuを表示・非表示しています。
表示・非表示にはanimationプロパティを使用しています。(transitionプロパティだと都合が悪いので…後述します)

共通のCSSその4(Maicromodal.js用)

/* -------------------------------------------------------------------- */
/*
/* Micromodal.js をドロワー向けに
/* -------------------------------------------------------------------- */
.nav__drawer {

  /* overlay|クラス付与で、どこでもタップ閉じる ------------------------------- */
  .nav__drawer-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.7);
    width: 100vw;
    height: 100vh;
    opacity: 0;
    visibility: hidden;
    transition: 0.3s;
    will-change: opacity,
      visibility;
  }

  &[aria-hidden="false"] .nav__drawer-overlay {
    opacity: 1;
    visibility: visible;
  }

  // メニューコンテンツ非表示
  .nav__drawer-container {
    display: none;
    overflow: hidden;
  }

  // クラス付与でメニューコンテンツ表示
  &.is-open .nav__drawer-container {
    display: inline;
  }
}

メニューコンテンツを内包している.nav__drawer-containerを、CSSの[displayプロパティ]で切り替えています。

メニュー閉じる役割をしている幕の.nav__drawer-overlayは、 [visibilityと opacity]を使うことで transitionプロパティが効くようにしています。(displayだとtransitionが効かない)

CSSに関しては以上となります。
nav本体のCSSに関しては、好みのCSSに変えていただければと思います。

JavaScript(ドロワー開閉用)

/* -------------------------------------------------------------------- */
/*
/* モーダル開閉
/* -------------------------------------------------------------------- */
(function () {

  /* 背景固定用 ------------------------------- */
  const backfaceFixed = (fixed) => {
    /**
     * 表示されているスクロールバーとの差分を計測し、背面固定時はその差分body要素に余白を生成する
     */
    const scrollbarWidth = window.innerWidth - document.body.clientWidth;
    document.body.style.paddingRight = fixed ? `${scrollbarWidth}px` : '';

    /**
     * スクロール位置を取得する要素を出力する(`html`or`body`)
     */
    const scrollingElement = () => {
      const browser = window.navigator.userAgent.toLowerCase();
      if ('scrollingElement' in document) return document.scrollingElement;
      if (browser.indexOf('webkit') > 0) return document.body;
      return document.documentElement;
    };

    /**
     * 変数にスクロール量を格納
     */
    const scrollY = fixed
      ? scrollingElement().scrollTop
      : parseInt(document.body.style.top || '0');

    /**
     * CSSで背面を固定
     */
    const styles = {
      height: '100vh',
      left: '0',
      overflow: 'hidden',
      position: 'fixed',
      top: `${scrollY * -1}px`,
      width: '100vw',
    };

    Object.keys(styles).forEach((key) => {
      document.body.style[key] = fixed ? styles[key] : '';
    });

    /**
     * 背面固定解除時に元の位置にスクロールする
     */
    if (!fixed) window.scrollTo(0, scrollY * -1);
  };

  // ========================================================================================

  const drawer_01 = document.getElementById('drawer-1');
  const drawer_open_button = document.getElementById('js_drawer_button_open');

  // メニューの開閉
  drawer_open_button.addEventListener("click", () => {
    if (!drawer_01.classList.contains("is-open")) {

      MicroModal.show("drawer-1", {
        disableScroll: true,
        awaitOpenAnimation: true,
        awaitCloseAnimation: true,

        onShow: (modal) => {
          drawer_open_button.classList.add("is-open");
          drawer_open_button.ariaExpanded = true; // スクリーンリーダーに伝える役割

          backfaceFixed(true); // 背景固定する

          console.info(`${modal.id} is shown`); // Optional
        },

        onClose: (modal) => {
          drawer_open_button.classList.remove("is-open");
          drawer_open_button.ariaExpanded = false; // スクリーンリーダーに伝える役割

          backfaceFixed(false); // 背景固定しない

          console.info(`${modal.id} is hidden`); // Optional
        }
      });
    } else {
      MicroModal.close("drawer-1")
    }
  });

}());

このJSには、
・ドロワーメニューを開いた際に背景固定するコードと、
・ナビゲーション開閉用のコードが書いてあります。

Maicromodalライブラリのオプションでも背景固定は可能ですが、サイトを表示するブラウザによっては背景固定がされないので付け加えています。

背景固定用コードはこちらを使わせていただきました。
以下、参考元です

参考元のコードは、背景固定をモジュール(コードの塊)として扱うためにimport文が使われていますが、import文を使うとローカル環境( file:// 〜〜〜 )で確認することが出来なくなるので注意が必要です。今回はモジュール化せずに背景固定をしています。

【 開閉ボタンが個別ver. 】の説明は、たぶん需要ないのでやめておきます(泣

注意点

Maicromodal.jsを使うにあたって、いくつか注意点があるので記しておきます。

is-openのクラス付与時のエラー

ドロワーまたはモーダルを閉じた際に、[is-open]が外れなくなる。
または [is-open]が点滅する。(付け外しが連続で行われている)
このエラーに遭遇した時は、Maicromodal.jsのオプション設定に問題があります。

awaitOpenAnimation: truefalse, // 開くときにアニメーション処理を待つかどうか
awaitCloseAnimation: truefalse, // 閉じるときに…同文

@keyframesを使ったanimationプロパティを使う場合は、

awaitOpenAnimation: true
awaitCloseAnimation: true に。

transitionプロパティを使う場合は、

awaitOpenAnimation: false
awaitCloseAnimation: false 
という設定が必要です。

is-open付与の挙動が上記の原因以外でもおかしい場合

私の場合ですが、
(オプションとの兼ね合いなのか)閉じる際のanimationプロパティをつけたものに対し、display: none;をつけると is-open付与の挙動がおかしくなることを確認してます。
animationプロパティとdisplay: none;との併用は避けた方がよさそうです。

キーボード操作時のTabキー移動が上手くいかない場合

ドロワーまたはモーダルで表示させるコンテンツは[display: none;]で消しておく必要があります。でないとTabキーでの移動が非表示のコンテンツを拾ってしまうため注意が必要です。
加えて、
[display: none;]にしている要素は transitionプロパティが効かないことにも気を付けてください。

モーダルコンテンツの中で、モーダルコンテンツの実装は無茶です。

背景固定をしなければ実装は、出来そうです。

背景固定をしたい場合、
モーダル内のモーダルを開くまでは出来ますが、
モーダル内のモーダルを閉じた際に、背景固定も解除されてしまうため、最初のモーダルの背景固定が効かなくなります。

背景固定しつつモーダル内でモーダルを作るには、
最初のモーダルはMicromodal.jsで背景固定しつつ開き、中のモーダルは以下のサイトを参考にすれば作れます。

まとめ

Maicromodal.jsを使うことで、アクセシビリティを配慮した制作をすることができるので、これからも使っていきたいですし、配慮されたWebサイトが広まるといいですね!

最後まで読んでいただき、ありがとうございました。

参考リンク

シェアしてもいいかも…と、思ったら

記事 一覧へ

コメントを書いてみる

CAPTCHA