skysan's programming notebook

コーディングして思ったことなどを気ままに

モーダルなコンポーネントのフォーカス管理

やりたいこと

Vue.jsでモーダルダイアログ表示中にTab移動した時、フォーカスがモーダルダイアログ外に行かないようにしたい。

※ただし、Webページ外(ブラウザのアドレスバーなど)からのTab移動は除く。

成果物

Before

Tab移動すると、ダイアログ外に移動してしまう。

移動順序: テキストボックス --> OKボタン --> キャンセルボタン --> アドレスバー --> 背後のテキストボックス

f:id:skysan:20191125010236g:plain
フォーカス移動:対処前

After

移動順序: テキストボックス --> OKボタン --> キャンセルボタン --> テキストボックス

f:id:skysan:20191125010123g:plain
フォーカス移動:対処後

ソースコード(GitHub)

実装方針

ダイアログの要素外に空のdiv要素を用意し、そこにフォーカスが当たったら、最初の要素にフォーカスを移動する。

1. 空のdiv要素の配置

TabキーもしくはShift + Tabキーでのフォーカス移動に対応するため、ダイアログの要素の前後に空のdiv要素を配置する。

<div tabindex="0" class="dummy"></div>

<!-- モーダルダイアログの要素 -->
<div class="modal-body">
  <input class="input-text" type="text" v-model="comment" ref="modalComment" />
</div>

<div tabindex="0" class="dummy"></div>

2. 空のdiv要素にフォーカスが移動したことを検知する

モーダルダイアログのコンポーネントが表示されているときのみフォーカスを検知する必要がある。Vueコンポーネントのライフサイクルに合わせて、イベント登録/破棄を行う。

// モーダルダイアログのSFCファイル
created() {
    document.addEventListener("focusin", this.checkFocus, false);
},
beforeDestroy() {
    document.removeEventListener("focusin", this.checkFocus, false);
}

3. 最初の要素にフォーカスを移動する

モーダルダイアログのフォーカス可能な最初の要素にフォーカス移動する。

// モーダルダイアログのSFCファイル
methods: {
    checkFocus: function(ev) {
      if (ev.target !== null && ev.target.className == 'dummy') {
        this.$refs.modalComment.focus();
      }
    }
}

所感

調査段階では、tabindex=-1にする方法も考えたが、この方法はDOM操作など影響範囲が少ないのでスマート。

参考記事

sukoyakarizumu.hatenablog.com