概要
アクセシビリティの観点から、モーダルのあるページではtab移動をjsを使って制御する必要がある。
(1)モーダルを開くと、tab移動がモーダル内でループする
(2)モーダルを閉じると、tabの位置が下のカーソル位置に戻る
(3)escキーでモーダルを閉じる
これらの処理を記述していない場合、タブはモーダルの背景を移動することになるため、使えないことになる。
jsでは、モーダルの開閉処理時に、以下のtabフォーカス処理を追加して実装する。
(1)モーダルを開くと、tab移動がモーダル内でループする
モーダルを開いた際にtab移動を制御する関数:handleModalTabTrapを使ってループさせる処理を実装
(2)モーダルを閉じると、tabの位置が下のカーソル位置に戻る
モーダルを開く前にカーソルの当たった位置を記憶する変数:lastFocuseTargetを用意しておく。
モーダルを閉じた時にそのlastFocuseTargetにカーソルを当てる。
(3)escキーでモーダルを閉じる
ecsキーで押した時の挙動をtabキーのハンドリングに含めることで、モーダルを閉じることができる。
コード
グローバル変数の準備
var lastFocuseTarget = null; //モーダルに入る直前のフォーカスポイント。モーダルを閉じた時に元に戻る時に使用
var KeyHandler = null; //tabキーの操作があったかを記憶する変数
モーダルを閉じる関数に追加する処理
function closeModal() {
・・・モーダルを閉じる処理・・・
//イベントリスナーの解除
if (KeyHandler) {
$(document).off('keydown', KeyHandler);
KeyHandler = null;
}
//モーダルを閉じた後にフォーカスを元の位置に戻す
if (lastFocuseTarget && typeof lastFocuseTarget.focus === 'function') {
try { $(lastFocuseTarget).focus(); }
catch (err) {$(document.body).focus(); }
} else {
$('button, a').blur(); //blurでフォーカスを外す
}
}
モーダルのセット関数
function setModal(){
$('body').on('click','.cmn_modal_btn', (e) => {
・・・モーダルを開く処理・・・
lastFocuseTarget = document.activeElement; //現在フォーカスが当たっているエレメントを保管
setModalTabFocus($(`#${target}`)); } //モーダル用のtabフォーカス関数を実行
// モーダルを閉じる
$('body').on('click','.cmn_modal_close, .cmn_modal_bg', (e) => {
closeModal();
return false;
});
}
モーダル内にいる時のループ処理
function handleModalTabTrap(e, $modal) {
var nodes = getFocusableElements($modal[0]); //$modal[0]はjQueryで抽出したhtmlコードそのもの
var first = nodes[0];
var last = nodes[nodes.length - 1];
var active = document.activeElement; //現在アクティブなエレメント
if (e.shiftKey) { //シフトキーを押したとき(tabが逆方向に移動するとき)
if (active === first || active === $modal[0]) { //最初のアイテムにフォーカスが当たっているとき(移動する前)
e.preventDefault(); //キーの移動動作を停止
$(last).focus();
}
} else { //tabの純方法
if (active === last) { //最後のアイテムにフォーカスが当たっているとき(移動する前)
e.preventDefault(); //キーの移動動作を停止
$(first).focus();
}
}
}
モーダル内にいる時のtabとescキーの処理
function setModalTabFocus($modal) {
if (!$modal.length) return;
var focusable = getFocusableElements($modal[0]); //関数で抽出した、フォーカス可能なアイテムの配列。
$(focusable[0]).focus();
KeyHandler = function(e) {
var key = e.key || e.keyCode;
if (key === 'Escape' || key === 'Esc' || key === 27) {
e.preventDefault();
closeModal(); //モーダルを閉じる処理を実行
return;
}
if (key === 'Tab' || key === 9) {
handleModalTabTrap(e, $modal); //モーダル内のタブの処理
}
};
$(document).on('keydown', KeyHandler);
}
フォーカス可能なエレメントを抽出する関数
function getFocusableElements(container) {
var selectors = 'a[href], input:not([disabled]):not([type="hidden"]), select:not([disabled]),
textarea:not([disabled]), button:not([disabled]), iframe, [tabindex]:not([tabindex="-1"])'; //主なtabターゲット
var $container = $(container);
if (!$container.length) return []; //エラー対応
return $container.find(selectors).filter(':visible').toArray();
//モーダルhtml内でtabターゲットが見つかれば配列に保管。:visibleは見えているものを対象(display:noneなどは対象にならない)
}