制作多个可访问的模式对话框
Making multiple accessible modal dialogs
我正在尝试将多个可访问的模式对话框构建到我正在创建的网站中。我一直在使用在以下 link: https://github.com/ireade/accessible-modal-dialog 处找到的代码。这很适合我的目的。键盘命令可以完全访问该框。但是我需要在站点的不同位置有多个对话框,每个对话框中有不同的信息。有谁知道我会如何改变 JS 来实现这一点?我试过了,但我对 JS 不是很好,也没有任何运气。作为奖励:你知道我如何在屏幕上逐渐将此对话框动画化为 scroll/appear 吗?感谢您提供的所有帮助!
库需要稍微重写才能真正可重用。但是,您可以按原样使用该库:-
- 向打开模式的按钮添加一个额外的 class(因此我们可以单独引用每个按钮,这也可以是一个 ID)
- 向对话框添加额外的 class(因此我们可以分别引用每个对话框)
- 创建新模态并为该新模态添加事件侦听器(还略微更改旧模态的引用)。
我在下面添加了一个 fiddle。在 JavaScript 中,我在进行了更改的地方添加了注释(滚动到底部可以看到 HTML 中包含的 JavaScript,这是我完成这项工作的唯一方法作为 fiddle,所有顶部 JavaScript 只是您引用的库。)。
另请注意 HTML 我添加了一个额外的按钮来打开第二个模态并添加第二个模态。密切注意按钮和模式上的 classes 以及它们与我添加评论的 JavaScript 的关系。
有问题尽管问。
正确的方法。
为了改进这个库,我会向按钮添加一个 data-target="modalID"
并让它自动将它们连接在一起。
创建一个将遵循以下步骤的函数(即 function modalInit()
):
- 查找具有特定 class (
.open-dialog
) 的所有按钮
- 看看它的
data-target
(模态的ID)
- 创建模式 (
new Dialog(IDofModalFromDataTarget, dialogOverlay);
)
- 添加事件侦听器。 (
DialogYouJustCreated.addEventListeners('buttonThatWeFoundTheDataIdOn', '.close-dialog')
可能看起来很吓人,但如果您将其分解为这些步骤,那将是一个很好的学习练习,然后您就可以完成它,以便将来无需任何额外代码即可添加模态。
如果您决定尝试这个,请随时 post 任何 fiddle,我会帮助您。
两个对话框的工作示例。
//Ignore this top part, scroll down to 'This is the page HTML'
function Dialog(dialogEl, overlayEl) {
this.dialogEl = dialogEl;
this.overlayEl = overlayEl;
this.focusedElBeforeOpen;
var focusableEls = this.dialogEl.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
this.focusableEls = Array.prototype.slice.call(focusableEls);
this.firstFocusableEl = this.focusableEls[0];
this.lastFocusableEl = this.focusableEls[ this.focusableEls.length - 1 ];
this.close(); // Reset
}
Dialog.prototype.open = function() {
var Dialog = this;
this.dialogEl.removeAttribute('aria-hidden');
this.overlayEl.removeAttribute('aria-hidden');
this.focusedElBeforeOpen = document.activeElement;
this.dialogEl.addEventListener('keydown', function(e) {
Dialog._handleKeyDown(e);
});
this.overlayEl.addEventListener('click', function() {
Dialog.close();
});
this.firstFocusableEl.focus();
};
Dialog.prototype.close = function() {
this.dialogEl.setAttribute('aria-hidden', true);
this.overlayEl.setAttribute('aria-hidden', true);
if ( this.focusedElBeforeOpen ) {
this.focusedElBeforeOpen.focus();
}
};
Dialog.prototype._handleKeyDown = function(e) {
var Dialog = this;
var KEY_TAB = 9;
var KEY_ESC = 27;
function handleBackwardTab() {
if ( document.activeElement === Dialog.firstFocusableEl ) {
e.preventDefault();
Dialog.lastFocusableEl.focus();
}
}
function handleForwardTab() {
if ( document.activeElement === Dialog.lastFocusableEl ) {
e.preventDefault();
Dialog.firstFocusableEl.focus();
}
}
switch(e.keyCode) {
case KEY_TAB:
if ( Dialog.focusableEls.length === 1 ) {
e.preventDefault();
break;
}
if ( e.shiftKey ) {
handleBackwardTab();
} else {
handleForwardTab();
}
break;
case KEY_ESC:
Dialog.close();
break;
default:
break;
}
};
Dialog.prototype.addEventListeners = function(openDialogSel, closeDialogSel) {
var Dialog = this;
var openDialogEls = document.querySelectorAll(openDialogSel);
for ( var i = 0; i < openDialogEls.length; i++ ) {
openDialogEls[i].addEventListener('click', function() {
Dialog.open();
});
}
var closeDialogEls = document.querySelectorAll(closeDialogSel);
for ( var i = 0; i < closeDialogEls.length; i++ ) {
closeDialogEls[i].addEventListener('click', function() {
Dialog.close();
});
}
};
//*****************This is the page HTML*********************//
var dialogOverlay = document.querySelector('.dialog-overlay'); //dialog overlay is used by both so we only need one reference to it here.
var navDialogEl1 = document.querySelector('.dialog1');//grab the first dialog element
var myDialog1 = new Dialog(navDialogEl1, dialogOverlay); //create a new dialog from the element 'navDialogEl1'.
myDialog1.addEventListeners('.open-dialog1', '.close-dialog'); //notice how I changed the open dialog class - I also added an extra class to the button that is related to this dialog with the same name
var navDialogEl2 = document.querySelector('.dialog2'); //grab the second dialog element
var myDialog2 = new Dialog(navDialogEl2, dialogOverlay); //create a new dialog for the second dialog element, notice how I use the same background (dialogOverlay).
myDialog2.addEventListeners('.open-dialog2', '.close-dialog'); //add an event listener to open this dialog. Yet again check the HTML I added an extra class to the second button 'open-dialog2'. Notice how I also used the same 'close-dialog' as any button with this calss should close all dialogs anyway.
.dialog-overlay {
z-index: 2;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
}
.dialog {
z-index: 3;
background-color: #fff;
padding: 20px;
text-align: center;
width: 90%;
max-width: 400px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.dialog-overlay[aria-hidden="true"],
.dialog[aria-hidden="true"] {
display: none;
}
.dialog-overlay:not([aria-hidden="true"]),
.dialog:not([aria-hidden="true"]) {
display: block;
}
.sr-only {
opacity: 0;
position: absolute;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
}
<header>
<div class="wrapper">
<h1><a href="https://ireade.github.io/accessible-modal-dialog/">Accessible Dialog</a></h1>
<button type="button" aria-label="Open Navigation" class="open-dialog1">open 1</button>
<button type="button" aria-label="Open Navigation" class="open-dialog2">open 2</button>
</div>
</header>
<div class="dialog dialog1" role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-description">
<h1 id="dialog-title">Dialog 1</h1>
<button type="button" aria-label="Close Navigation" class="close-dialog"> Close </button>
</div>
<div class="dialog dialog2" role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-description">
<h1 id="dialog-title">Dialog 2</h1>
<button type="button" aria-label="Close Navigation" class="close-dialog"> Close</button>
</div>
<div class="wrapper body-wrapper">
<p><a href="https://github.com/ireade/accessible-modal-dialog">View Source</a> | <a href="=https://bitsofco.de/accessible-modal-dialog">Blog Post</a>
<p>Venmo tacos ennui hoodie lomo tousled. Meh irony blue bottle brooklyn paleo. Post-ironic PBR&B blue bottle, iPhone meh ennui forage salvia normcore neutra chicharrones gentrify. Banjo jean shorts selfies, try-hard venmo before they sold out 8-bit gluten-free pinterest sustainable messenger bag you probably haven't heard of them poutine. Scenester farm-to-table craft beer, knausgaard leggings letterpress brunch asymmetrical. Brooklyn you probably haven't heard of them typewriter flannel. Etsy austin venmo, knausgaard green juice squid butcher kombucha literally beard jean shorts VHS tote bag.</p>
<p>Artisan bushwick pop-up, biodiesel viral semiotics cliche pinterest fingerstache godard lo-fi franzen forage. Hammock narwhal ethical, kogi put a bird on it pork belly bushwick photo booth +1 master cleanse pinterest direct trade vegan tofu. Small batch cold-pressed paleo wolf, skateboard asymmetrical cred vegan pickled pinterest freegan. Man bun portland man braid, thundercats swag keffiyeh scenester semiotics put a bird on it keytar four loko beard pour-over. Meh VHS biodiesel actually poutine, normcore neutra beard narwhal hoodie. Synth sustainable cred meditation health goth tousled. Post-ironic cornhole scenester whatever, authentic bushwick keffiyeh venmo kinfolk chia.</p>
<p>Sriracha XOXO master cleanse lomo blue bottle, banh mi fashion axe man braid flexitarian. Meggings pug ennui, chambray 8-bit celiac gentrify. Bitters direct trade chia semiotics. Synth fixie mixtape, health goth four dollar toast vinyl 3 wolf moon VHS schlitz. Drinking vinegar letterpress VHS poutine, venmo cronut distillery artisan. Everyday carry craft beer butcher DIY. Normcore affogato chillwave, thundercats banh mi fingerstache keytar pop-up four loko four dollar toast.</p>
</div>
<div class="dialog-overlay" tabindex="-1"></div>
<!--JavaScript moved from here to the bottom of the JavaScript section-->
我正在尝试将多个可访问的模式对话框构建到我正在创建的网站中。我一直在使用在以下 link: https://github.com/ireade/accessible-modal-dialog 处找到的代码。这很适合我的目的。键盘命令可以完全访问该框。但是我需要在站点的不同位置有多个对话框,每个对话框中有不同的信息。有谁知道我会如何改变 JS 来实现这一点?我试过了,但我对 JS 不是很好,也没有任何运气。作为奖励:你知道我如何在屏幕上逐渐将此对话框动画化为 scroll/appear 吗?感谢您提供的所有帮助!
库需要稍微重写才能真正可重用。但是,您可以按原样使用该库:-
- 向打开模式的按钮添加一个额外的 class(因此我们可以单独引用每个按钮,这也可以是一个 ID)
- 向对话框添加额外的 class(因此我们可以分别引用每个对话框)
- 创建新模态并为该新模态添加事件侦听器(还略微更改旧模态的引用)。
我在下面添加了一个 fiddle。在 JavaScript 中,我在进行了更改的地方添加了注释(滚动到底部可以看到 HTML 中包含的 JavaScript,这是我完成这项工作的唯一方法作为 fiddle,所有顶部 JavaScript 只是您引用的库。)。
另请注意 HTML 我添加了一个额外的按钮来打开第二个模态并添加第二个模态。密切注意按钮和模式上的 classes 以及它们与我添加评论的 JavaScript 的关系。
有问题尽管问。
正确的方法。
为了改进这个库,我会向按钮添加一个 data-target="modalID"
并让它自动将它们连接在一起。
创建一个将遵循以下步骤的函数(即 function modalInit()
):
- 查找具有特定 class (
.open-dialog
) 的所有按钮
- 看看它的
data-target
(模态的ID) - 创建模式 (
new Dialog(IDofModalFromDataTarget, dialogOverlay);
) - 添加事件侦听器。 (
DialogYouJustCreated.addEventListeners('buttonThatWeFoundTheDataIdOn', '.close-dialog')
可能看起来很吓人,但如果您将其分解为这些步骤,那将是一个很好的学习练习,然后您就可以完成它,以便将来无需任何额外代码即可添加模态。
如果您决定尝试这个,请随时 post 任何 fiddle,我会帮助您。
两个对话框的工作示例。
//Ignore this top part, scroll down to 'This is the page HTML'
function Dialog(dialogEl, overlayEl) {
this.dialogEl = dialogEl;
this.overlayEl = overlayEl;
this.focusedElBeforeOpen;
var focusableEls = this.dialogEl.querySelectorAll('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex="0"]');
this.focusableEls = Array.prototype.slice.call(focusableEls);
this.firstFocusableEl = this.focusableEls[0];
this.lastFocusableEl = this.focusableEls[ this.focusableEls.length - 1 ];
this.close(); // Reset
}
Dialog.prototype.open = function() {
var Dialog = this;
this.dialogEl.removeAttribute('aria-hidden');
this.overlayEl.removeAttribute('aria-hidden');
this.focusedElBeforeOpen = document.activeElement;
this.dialogEl.addEventListener('keydown', function(e) {
Dialog._handleKeyDown(e);
});
this.overlayEl.addEventListener('click', function() {
Dialog.close();
});
this.firstFocusableEl.focus();
};
Dialog.prototype.close = function() {
this.dialogEl.setAttribute('aria-hidden', true);
this.overlayEl.setAttribute('aria-hidden', true);
if ( this.focusedElBeforeOpen ) {
this.focusedElBeforeOpen.focus();
}
};
Dialog.prototype._handleKeyDown = function(e) {
var Dialog = this;
var KEY_TAB = 9;
var KEY_ESC = 27;
function handleBackwardTab() {
if ( document.activeElement === Dialog.firstFocusableEl ) {
e.preventDefault();
Dialog.lastFocusableEl.focus();
}
}
function handleForwardTab() {
if ( document.activeElement === Dialog.lastFocusableEl ) {
e.preventDefault();
Dialog.firstFocusableEl.focus();
}
}
switch(e.keyCode) {
case KEY_TAB:
if ( Dialog.focusableEls.length === 1 ) {
e.preventDefault();
break;
}
if ( e.shiftKey ) {
handleBackwardTab();
} else {
handleForwardTab();
}
break;
case KEY_ESC:
Dialog.close();
break;
default:
break;
}
};
Dialog.prototype.addEventListeners = function(openDialogSel, closeDialogSel) {
var Dialog = this;
var openDialogEls = document.querySelectorAll(openDialogSel);
for ( var i = 0; i < openDialogEls.length; i++ ) {
openDialogEls[i].addEventListener('click', function() {
Dialog.open();
});
}
var closeDialogEls = document.querySelectorAll(closeDialogSel);
for ( var i = 0; i < closeDialogEls.length; i++ ) {
closeDialogEls[i].addEventListener('click', function() {
Dialog.close();
});
}
};
//*****************This is the page HTML*********************//
var dialogOverlay = document.querySelector('.dialog-overlay'); //dialog overlay is used by both so we only need one reference to it here.
var navDialogEl1 = document.querySelector('.dialog1');//grab the first dialog element
var myDialog1 = new Dialog(navDialogEl1, dialogOverlay); //create a new dialog from the element 'navDialogEl1'.
myDialog1.addEventListeners('.open-dialog1', '.close-dialog'); //notice how I changed the open dialog class - I also added an extra class to the button that is related to this dialog with the same name
var navDialogEl2 = document.querySelector('.dialog2'); //grab the second dialog element
var myDialog2 = new Dialog(navDialogEl2, dialogOverlay); //create a new dialog for the second dialog element, notice how I use the same background (dialogOverlay).
myDialog2.addEventListeners('.open-dialog2', '.close-dialog'); //add an event listener to open this dialog. Yet again check the HTML I added an extra class to the second button 'open-dialog2'. Notice how I also used the same 'close-dialog' as any button with this calss should close all dialogs anyway.
.dialog-overlay {
z-index: 2;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.7);
}
.dialog {
z-index: 3;
background-color: #fff;
padding: 20px;
text-align: center;
width: 90%;
max-width: 400px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.dialog-overlay[aria-hidden="true"],
.dialog[aria-hidden="true"] {
display: none;
}
.dialog-overlay:not([aria-hidden="true"]),
.dialog:not([aria-hidden="true"]) {
display: block;
}
.sr-only {
opacity: 0;
position: absolute;
clip: rect(1px 1px 1px 1px);
clip: rect(1px, 1px, 1px, 1px);
}
<header>
<div class="wrapper">
<h1><a href="https://ireade.github.io/accessible-modal-dialog/">Accessible Dialog</a></h1>
<button type="button" aria-label="Open Navigation" class="open-dialog1">open 1</button>
<button type="button" aria-label="Open Navigation" class="open-dialog2">open 2</button>
</div>
</header>
<div class="dialog dialog1" role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-description">
<h1 id="dialog-title">Dialog 1</h1>
<button type="button" aria-label="Close Navigation" class="close-dialog"> Close </button>
</div>
<div class="dialog dialog2" role="dialog" aria-labelledby="dialog-title" aria-describedby="dialog-description">
<h1 id="dialog-title">Dialog 2</h1>
<button type="button" aria-label="Close Navigation" class="close-dialog"> Close</button>
</div>
<div class="wrapper body-wrapper">
<p><a href="https://github.com/ireade/accessible-modal-dialog">View Source</a> | <a href="=https://bitsofco.de/accessible-modal-dialog">Blog Post</a>
<p>Venmo tacos ennui hoodie lomo tousled. Meh irony blue bottle brooklyn paleo. Post-ironic PBR&B blue bottle, iPhone meh ennui forage salvia normcore neutra chicharrones gentrify. Banjo jean shorts selfies, try-hard venmo before they sold out 8-bit gluten-free pinterest sustainable messenger bag you probably haven't heard of them poutine. Scenester farm-to-table craft beer, knausgaard leggings letterpress brunch asymmetrical. Brooklyn you probably haven't heard of them typewriter flannel. Etsy austin venmo, knausgaard green juice squid butcher kombucha literally beard jean shorts VHS tote bag.</p>
<p>Artisan bushwick pop-up, biodiesel viral semiotics cliche pinterest fingerstache godard lo-fi franzen forage. Hammock narwhal ethical, kogi put a bird on it pork belly bushwick photo booth +1 master cleanse pinterest direct trade vegan tofu. Small batch cold-pressed paleo wolf, skateboard asymmetrical cred vegan pickled pinterest freegan. Man bun portland man braid, thundercats swag keffiyeh scenester semiotics put a bird on it keytar four loko beard pour-over. Meh VHS biodiesel actually poutine, normcore neutra beard narwhal hoodie. Synth sustainable cred meditation health goth tousled. Post-ironic cornhole scenester whatever, authentic bushwick keffiyeh venmo kinfolk chia.</p>
<p>Sriracha XOXO master cleanse lomo blue bottle, banh mi fashion axe man braid flexitarian. Meggings pug ennui, chambray 8-bit celiac gentrify. Bitters direct trade chia semiotics. Synth fixie mixtape, health goth four dollar toast vinyl 3 wolf moon VHS schlitz. Drinking vinegar letterpress VHS poutine, venmo cronut distillery artisan. Everyday carry craft beer butcher DIY. Normcore affogato chillwave, thundercats banh mi fingerstache keytar pop-up four loko four dollar toast.</p>
</div>
<div class="dialog-overlay" tabindex="-1"></div>
<!--JavaScript moved from here to the bottom of the JavaScript section-->