これはおそらくアクセシビリティの問題であり、そのままにしておくのが最善であると認識していますが、タブサイクルでタブがアドレスバーにアクセスしないようにすることが可能かどうかを調べたいと思います.
私のアプリケーションには、入力領域を循環する別の方法がありますが、多くの新規ユーザーは本能的にタブを使用しようとし、期待どおりに動作しません。
これはおそらくアクセシビリティの問題であり、そのままにしておくのが最善であると認識していますが、タブサイクルでタブがアドレスバーにアクセスしないようにすることが可能かどうかを調べたいと思います.
私のアプリケーションには、入力領域を循環する別の方法がありますが、多くの新規ユーザーは本能的にタブを使用しようとし、期待どおりに動作しません。
これは、最大タブ インデックスを見つける必要がない一般的な jquery 実装です。このコードは、DOM で要素を追加または削除した場合にも機能することに注意してください。
$('body').on('keydown', function (e) {
var jqTarget = $(e.target);
if (e.keyCode == 9) {
var jqVisibleInputs = $(':input:visible');
var jqFirst = jqVisibleInputs.first();
var jqLast = jqVisibleInputs.last();
if (!e.shiftKey && jqTarget.is(jqLast)) {
e.preventDefault();
jqFirst.focus();
} else if (e.shiftKey && jqTarget.is(jqFirst)) {
e.preventDefault();
jqLast.focus();
}
}
});
ただし、上記のコードは可視入力でのみ機能することに注意してください。一部の要素は、入力されていなくてもドキュメントの activeElement になる可能性があるため、その場合は、それらを$(':input:visible')
セレクターに追加することを検討する必要があります。
フォーカス要素にスクロールするコードは追加しませんでした。これは、すべての人にとって望ましい動作ではない可能性があるためです...必要な場合は、への呼び出しの後に追加するだけですfocus()
tabindex
グローバル属性を使用して、タブの順序 (およびどの要素がフォーカスを取得できるか) を制御できます。
ただし、この属性を使用すると、ユーザーがページの制御下にない別のコンテキスト (ブラウザーのアドレス バーなど) にタブで移動することを防ぐことはできません。(ただし、JavaScript との組み合わせでは可能かもしれません。)
そのような (悪!) ユース ケースでは、キーボード トラップを調べる必要があります。
WCAG 2.0 にはガイドラインがあります: 2.1.2 No Keyboard Trap。SC 2.1.2 の理解では、このガイドラインの「テクニックと失敗」を見つけることができます。
ですから、そのようなトラップがどのように可能になるかについて、いくつかのアイデアが得られるかもしれません。
m-albertソリューションを使用しましたが、機能します。しかし、私の場合、tabindexプロパティを制御しません。私の意図は、ユーザーがページの最後のコントロールを離れたときに、ページの上部(最初のコントロール)にあるツールバーにフォーカスを設定することです。
$(':input:visible').last().on('keydown', function (e) {
if (e.keyCode == 9 && e.shiftKey == false) {
e.preventDefault();
$('html, body').animate({
scrollTop: 0
}, 500);
$(':input:visible', context).first().focus();
}
});
contextは、任意の Jquery オブジェクト、セレクター、さらにはdocumentにすることも、省略することもできます。
もちろん、スクロールアニメーションはオプションです。
これがまだ問題であるかどうかはわかりませんが、jQuery を使用しない独自のソリューションを実装しました。これは、すべての要素が tabindex="0" で、DOM が変更される可能性がある場合に使用できます。タブサイクリングを tabindex 要素を含む特定の要素に制限したい場合は、コンテキストに追加の引数を追加しました。
引数に関するいくつかの簡単なメモ:
minはmax以下である必要があり、contextSelectorはオプションの文字列であり、使用する場合は有効なセレクターになります。contextSelectorが無効なセレクターまたはどの要素とも一致しないセレクターである場合、ドキュメントオブジェクトがコンテキストとして使用されます。
function PreventAddressBarTabCyle(min, max, contextSelector) {
if( isNaN(min) ) throw new Error('Invalid argument: first argument needs to be a number type.')
if( isNaN(max) ) throw new Error('Invalid argument: second argument needs to be a number type.')
if( max < min ) throw new Error('Invalid arguments: first argument needs to be less than or equal to the second argument.')
min = min |0;
max = max |0;
var isDocumentContext = typeof(contextSelector) != 'string' || contextSelector == '';
if( min == max ) {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindexElements = context.querySelectorAll('[tabindex]');
if( tabindexElements.length <= 0 ) return;
var targetIndex = -1;
for(var i = 0; i < tabindexElements.length; i++) {
if( e.target == tabindexElements[i] ) {
targetIndex = i;
break;
}
}
// Check if tabbing backward and reached first element
if( e.shiftKey == true && targetIndex == 0 ) {
e.preventDefault();
tabindexElements[tabindexElements.length-1].focus();
}
// Check if tabbing forward and reached last element
if( e.shiftKey == false && targetIndex == tabindexElements.length-1 ) {
e.preventDefault();
tabindexElements[0].focus();
}
};
} else {
var tabCycleListener = function(e) {
if( e.keyCode != 9 ) return;
var context = isDocumentContext ? document : document.querySelector(contextSelector);
if( !context && !('querySelectorAll' in context) ) {
context = document;
}
var tabindex = parseInt(e.target.getAttribute('tabindex'));
if( isNaN(tabindex) ) return;
// Check if tabbing backward and reached first element
if (e.shiftKey == true && tabindex == min) {
e.preventDefault();
context.querySelector('[tabindex="' + max + '"]').focus();
}
// Check if tabbing forward and reached last element
else if (e.shiftKey == false && tabindex == max) {
e.preventDefault();
context.querySelector('[tabindex="' + min + '"]').focus();
}
};
}
document.body.addEventListener('keydown', tabCycleListener, true);
}
その他の注意事項:
minがmaxと等しい場合、コンテキスト内の最後の要素に到達するまでタブ サイクリングが自然に発生します。minが厳密にmaxより小さい場合、minまたはmaxに達するまで、タブ移動は自然に繰り返されます。後方にタブ移動して最小値に到達した場合、または前方にタブ移動して最大値に到達した場合、タブ移動はそれぞれ最小要素または最大要素に循環します。
使用例:
// For all tabindex elements inside the document
PreventAddressBarTabCyle(0,0);
// Same as above
PreventAddressBarTabCyle(1,1);
// NOTE: if min == max, the tabindex value doesn't matter
// it matches all elements with the tabindex attribute
// For all tabindex elements between 1 and 15
PreventAddressBarTabCyle(1,15);
// For tabindex elements between 1 and 15 inside
// the first element that matches the selector: .some-form
PreventAddressBarTabCyle(1,15, '.some-form');
// For all tabindex elements inside the element
// that matches the selector: .some-form2
PreventAddressBarTabCyle(1,1, '.some-form2');