HTMLで自前モーダルウィンドウをやるときのフォーカス制御

このようなDOM操作でモーダルウィンドウ風の要素を作るときのフォーカス制御の話。

モーダルウィンドウの表示時は、背景を覆うようにスクリーン要素を置いてクリックできないようにすることが多い。しかし背景に<button>や<input>がある場合は、キーボードのtabでフォーカスして操作ができてしまう。それを防ぐ。

focusinイベントをキャッチしてフォーカス制御する

フォーカスのイベントにはfocus/blurの他にfocusin/focusoutイベントがある。

focus/blurイベントはバブリング(子要素のイベントが親要素に伝播)しないが、focusin/focusoutイベントはバブリングし、自分自身がフォーカスされない要素でも発生する。

次のようにbodyと#screenにfocusinイベントを設定する。

var isShowDialog = false
var focusOnDialog = false

document.body.addEventListener("focusin", function(){
    //モーダルウィンドウ表示中で、
    //フォーカスを取得したのがモーダル上でない場合フォーカスを外す。
    if(isShowDialog && !focusOnDialog){
        document.activeElement.blur()
    }
    focusOnDialog = false;
})

document.getEmementById("screen").addEventListener("focusin", function(){
    //モーダル上の要素がフォーカスされた
    focusOnDialog = true;
})

//モーダルウィンドウを表示する関数
function showDialog(){
    // ~
    // 表示の処理
    // ~

    document.activeElement.blur()    
    isShowDialog = true
}

モーダルウィンドウの表示中は、フォーカス移動をbodyのfocusinイベントで検出し、フォーカスを外す。

モーダルウィンドウ上の要素の場合は#screen.focusin → body.focusinの順で発火するので、フラグを立てることで背景のフォーカスだけを無効にする。

もちろんモーダルウィンドウを表示するときにも背景のフォーカスは外す。また記述していないが、このときモーダルウィンドウ上のフォーム要素を探索してフォーカスしてやるのもよい。

この方法はbodyとスクリーン要素にイベントを設定すれば対応できるので、コンテンツの内容に影響を受けずに実装が可能。

他の方法

以下は試行錯誤で却下した方法。

disabled属性をつける

モーダルウィンドウの表示時に<input>や<button>を探索してdisabled属性を付けるというもの。ただし既にdisabled属性を使っている場合は、後で元の状態を復元する必要がある。さらにフレームワークなどでバインドしている場合はDOMの直接変更は避けたい。

tabindex=-1をつける

クリックやタッチはスクリーンに阻まれてできないので、キーボードによるタブ移動を無効にすれば操作を防げる。しかしdisabledと同様に復元が必要。

pointer-event:noneをつける

このスタイルは親要素に設定すると子要素もクリックがきなくなる。しかしキーボードによる操作は防いでくれない。

背景を非表示に

いっそ背景の親要素に display:none や visibility:hidden を設定してしまえば操作はできなくなる。しかしいきなり背景が消えるのは見栄えが良くない。

inert属性

表示はするが操作は受け付けず、全てのタグに設定できて、親から子に伝播する属性。Windows Form の Enabledプロパティのような属性。残念ながら仕様策定段階でまだ実用できなさそうだ。

コメント