この記事では、「aria-modal」属性を使用せずにアクセス可能なモーダルを実装する方法を説明します。
ちょっとした理論!
「Aria-modal」は、現在のダイアログの下のWebコンテンツが相互運用可能(不活性)ではないことを支援技術(スクリーンリーダーなど)に伝えるために使用される属性です。言い換えると、モーダルの下にある要素は、クリック、TAB / SHIFT + TABナビゲーション、またはセンサーデバイスのスワイプにフォーカスを受け取るべきではありません。
しかし、なぜモーダルウィンドウに「aria-modal」を使用できないのでしょうか。
いくつかの理由があります:
- スクリーンリーダーではサポートされていません
- 疑似クラスでは無視されます ":before /:after"
実装に移りましょう。
実装
開発を開始するには、使用可能なモーダルウィンドウに必要なプロパティを選択する必要があります。
- モーダルウィンドウの外側にあるすべてのインタラクティブ要素は、ユーザー操作のためにブロックする必要があります:クリック、フォーカスなど...
- ナビゲーションは、ブラウザシステムコンポーネントとモーダル自体のコンテンツを介してのみ利用可能である必要があります(モーダル外のすべてのコンテンツは無視する必要があります)
ブランク
モーダルウィンドウを作成するための段階的な説明に時間を無駄にしないように、空白を使用します。
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<button type="button" id="infoBtn" class="btn"> Standart button </button>
<button type="button" id="openBtn"> Open modal window</button>
<div role="button" tabindex="0" id="infoBtn" class="btn"> Custom button </button>
</div>
<div>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Deserunt maxime tenetur sint porro tempore aperiam! Eaque tempore repudiandae culpa omnis placeat, fugit nostrum quisquam in ipsa odit accusamus illum velit?
</div>
<div id="modalWindow" class="modal">
<div>
<button type="button" id="closeBtn" class="btn-close">Close</button>
<h2>Modal window</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Expedita, doloribus.</p>
</div>
</div>
</body>
</html>
スタイル:
.modal {
position: fixed;
font-family: Arial, Helvetica, sans-serif;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.8);
z-index: 99999;
transition: opacity 400ms ease-in;
display: none;
pointer-events: none;
}
.active{
display: block;
pointer-events: auto;
}
.modal > div {
width: 400px;
position: relative;
margin: 10% auto;
padding: 5px 20px 13px 20px;
border-radius: 10px;
background: #fff;
}
.btn-close {
padding: 5px;
position: absolute;
right: 10px;
border: none;
background: red;
color: #fff;
box-shadow: 0 0 10px rgba(0,0,0,0.5);
}
.btn {
display: inline-block;
border: 1px solid #222;
padding: 3px 10px;
background: #ddd;
box-sizing: border-box;
}
JS:
let modaWindow = document.getElementById('modalWindow');
document.getElementById('openBtn').addEventListener('click', function() {
modaWindow.classList.add('active');
});
document.getElementById('closeBtn').addEventListener('click', function() {
modaWindow.classList.remove('active');
});
ページを開き、「TAB / SHIFT + TAB」キーを使用してモーダルウィンドウの背後にある要素に移動しようとすると、添付の図に示すように、これらの要素にフォーカスが表示されます。
この問題を解決するには、すべてのインタラクティブ要素に「tabindex」属性をマイナス1の値で割り当てる必要があります。
1.さらに作業を進めるために、次のプロパティとメソッドを使用してクラス「modalWindow」を作成します。
- doc-ページドキュメント。モーダルウィンドウを作成します
- モーダル-モーダルウィンドウのコンテナ
- InteractiveElementsList-インタラクティブ要素の配列
- blockElementsList-ページブロック要素の配列
- コンストラクター-クラスのコンストラクター
- create-モーダルウィンドウの作成に使用されるメソッド
- remove-モーダルを削除するために使用される方法
2.コンストラクターを実装しましょう。
constructor(doc, modal) {
this.doc = doc;
this.modal = modal;
this.interactiveElementsList = [];
this.blockElementsList = [];
}
「InteractiveElementsList」と「blockElementsList」は、モーダルの作成時に変更されたページ要素を含めるために必要です。
3.フォーカスを持つことができるすべての要素のリストを格納する定数を作成します。
const INTERECTIVE_SELECTORS = ['a', 'button', 'input', 'textarea', '[tabindex]'];
4.「create」メソッドで、セレクターに一致するすべての要素を選択し、すべての「tabindex = -1」を設定します(すでにこの値を持つ要素は無視します)
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
ナビゲーションに(モバイルプログラムで)特別なキーまたはジェスチャーを使用する場合にも同様の問題が発生します。この場合、インタラクティブな要素だけでなく、テキストでもナビゲートできます。これを修正するには、
5を追加する必要があります。ここでセレクターを保持するための配列を作成する必要はありません。「body」ノードのすべての子を取得するだけです。
let children = this.doc.body.children;
6. 4番目のステップはステップ2と同様ですが、「aria-hidden」を使用するだけです。
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
完了した「作成」メソッド:
create() {
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
let children = this.doc.body.children;
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
}
7. 6番目のステップでは、逆の「create」メソッドを実装します。
remove() {
let element;
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('tabindex', '0');
}
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('aria-gidden', 'false');
}
}
8.これを機能させるには、「modalWindow」クラスのインスタンスを作成し、「create」メソッドと「remove」メソッドを呼び出す必要があります。
let modaWindow = document.getElementById('modalWindow');
const modal = new modalWindow(document, modaWindow);
document.getElementById('openBtn').addEventListener('click', function() {
modaWindow.classList.add('active');
// modal.create();
});
document.getElementById('closeBtn').addEventListener('click', function() {
modaWindow.classList.remove('active');
// modal.remove();
});
完全なクラスコード:
class modalWindow{
constructor(doc, modal) {
this.doc = doc;
this.modal = modal;
this.interactiveElementsList = [];
this.blockElementsList = [];
}
create() {
let elements = this.doc.querySelectorAll(INTERECTIVE_SELECTORS.toString());
let element;
for (let i = 0; i < elements.length; i++) {
element = elements[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('tabindex') !== '-1') {
element.setAttribute('tabindex', '-1');
this.interactiveElementsList.push(element);
}
}
}
let children = this.doc.body.children;
for (let i = 0; i < children.length; i++) {
element = children[i];
if (!this.modal.contains(element)) {
if (element.getAttribute('aria-hidden') !== 'true') {
element.setAttribute('aria-hidden', 'true');
this.blockElementsList.push(element);
}
}
}
}
remove() {
let element;
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('tabindex', '0');
}
while(this.interactiveElementsList.length !== 0) {
element = this.interactiveElementsList.pop();
element.setAttribute('aria-gidden', 'false');
}
}
PS
テキスト要素のナビゲーションに関する問題がモバイルデバイスで解決されない場合は、次の選択を使用できます。
const BLOCKS_SELECTORS = ['div', 'header', 'main', 'section', 'footer'];
let children = this.doc.querySelectorAll(BLOCKS_SELECTORS .toString());