我正在尝试创建一个可访问的导航菜单,并且我想将焦点集中在每个面板中。当我单击汉堡包按钮时,它将打开显示按钮或链接列表的面板。我希望能够通过选项卡/向上和向下箭头,并根据其是第一个还是最后一个可聚焦元素再次循环。
如果它只是一个级别的导航链接,我知道我可以获取所有可聚焦元素并识别第一个和最后一个同级元素,并添加一个事件监听器 keydown 来循环,但我的问题是这些导航链接有子级别,我'我不太确定如何处理它。
在我的 codepen 中,我创建了一个函数,在其中获取面板的 id 并获取可聚焦元素的数组并将其设置在地图中。我不确定这是否是正确的方法,因为我的想法是确定哪个面板当前处于活动状态,然后从地图中获取可聚焦元素的数组,找到第一个/最后一个可聚焦元素,然后添加事件侦听器以检查正在使用的元素聚焦直到找到第一个或最后一个,然后循环。但当我编码的时候,感觉一点都不对劲。
const navigation = document.getElementsByClassName("n-navigation")[0];
const navigationMobile= navigation.getElementsByClassName("n-navigation__mobile")[0];
const navigationBlur= navigation.getElementsByClassName("n-navigation__blur")[0];
const hamburgerMenuBtn = navigation.firstElementChild;
hamburgerMenuBtn.addEventListener("click", openHamburgerMenu);
const mainContainer = navigationMobile.firstElementChild;
mainContainer.id = "main-container";
const closeMenuBtn = mainContainer.firstElementChild;
closeMenuBtn.addEventListener("click", closeMenu);
const mainList = mainContainer.getElementsByTagName("ul")[0];
const triggers = mainList.querySelectorAll("button.n-navigation__mobile__mainContainer__mainList__trigger, button.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__trigger");
const previousBtns = mainList.querySelectorAll("button.n-navigation__mobile__mainContainer__mainList__panel__subContainer__previous, button.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel__container__previous");
let activePanel;
let activeContainer;
let focusableMap = new Map();
for (let a=0; a < triggers.length; a++) {
triggers[a].id = "trigger-id-" + a;
triggers[a].addEventListener("click", nextTopic);
let panel = triggers[a].nextElementSibling;
panel.id = "panel-id-" + a;
triggers[a].setAttribute("aria-controls", panel.id);
}
for (let a=0; a < previousBtns.length; a++) {
previousBtns[a].addEventListener("click", previousTopic);
}
function openHamburgerMenu() {
navigation.classList.add("n-navigation--active");
navigationBlur.classList.remove("n-navigation__blur--hide");
navigationBlur.classList.add("n-navigation__blur--show");
this.setAttribute("aria-expanded", "true");
if (!focusableMap.has(mainContainer.id)) {
let container = mainContainer.getAttribute('class');
addFocusableToMap(mainContainer, container);
}
}
function closeMenu() {
navigation.classList.remove("n-navigation--active");
navigationBlur.classList.remove("n-navigation__blur--show");
navigationBlur.classList.add("n-navigation__blur--hide");
hamburgerMenuBtn.setAttribute("aria-expanded", "false");
}
function nextTopic() {
console.log("next topic");
let panel = this.nextElementSibling;
let container = panel.firstElementChild.getAttribute('class');
if (!focusableMap.has(panel.id)) addFocusableToMap(panel, container);
if (this.classList.contains("n-navigation__mobile__mainContainer__mainList__trigger")) {
mainList.parentElement.classList.add("n-navigation__mobile__mainContainer--right");
panel.classList.add("n-navigation__mobile__mainContainer__mainList__panel--active");
} else {
activeContainer = activePanel;
activePanel.classList.add("n-navigation__mobile__mainContainer__mainList__panel--right");
panel.classList.add("n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel--active");
}
activePanel = panel;
}
function previousTopic() {
console.log("previous topic");
if (this.classList.contains("n-navigation__mobile__mainContainer__mainList__panel__subContainer__previous")) {
mainList.parentElement.classList.remove("n-navigation__mobile__mainContainer--right");
activePanel.classList.remove("n-navigation__mobile__mainContainer__mainList__panel--active");
activePanel = null;
} else {
activeContainer.classList.remove("n-navigation__mobile__mainContainer__mainList__panel--right");
activePanel.classList.remove("n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel--active");
activePanel = activeContainer;
activeContainer = null;
}
}
function addFocusableToMap(panel, container) {
const focusable = panel.querySelectorAll("." + container + " > button, ." + container + " > ul > li > button, ." + container + " > ul > li > a");
const newFocusable = [];
for (let a=0; a < focusable.length; a++) {
focusable[a].dataset.triggerid = panel.id;
newFocusable.push(focusable[a]);
}
focusableMap.set(panel.id, focusable);
}
body {
margin: 0;
}
.n-navigation {
display: grid;
height: 4.375em;
grid-template-columns: auto 1fr auto;
align-items: center;
padding: 1em;
background-color: #f7f9fa;
}
@media screen and (min-width: 50em) {
.n-navigation__hamburgerMenu {
display: none;
}
}
.n-navigation__logo {
text-align: center;
}
@media screen and (min-width: 50em) {
.n-navigation__logo {
text-align: left;
}
}
.n-navigation__mobile {
height: 100vh;
width: 20em;
background-color: white;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
transform: translateX(-20em);
visibility: hidden;
z-index: 0;
transition: transform 350ms, z-index 0s, visibility 0s;
transition-delay: 0s, 350ms, 350ms;
}
@media screen and (min-width: 50em) {
.n-navigation__mobile {
display: none;
}
}
.n-navigation__mobile__mainContainer {
padding: 1.375em 1.5em;
transform: translateX(0);
transition: transform 350ms;
visibility: visible;
}
.n-navigation__mobile__mainContainer ul {
list-style-type: none;
padding-left: 0;
}
.n-navigation__mobile__mainContainer__close {
display: block;
margin-left: auto;
}
.n-navigation__mobile__mainContainer__mainList {
margin-top: 2.25em;
margin-bottom: 3em;
}
.n-navigation__mobile__mainContainer__mainList__trigger {
display: flex;
width: 100%;
background-color: transparent;
color: #1d1d1f;
text-decoration: none;
border: none;
padding: 0.5em 0;
justify-content: space-between;
align-items: center;
}
.n-navigation__mobile__mainContainer__mainList__trigger:hover, .n-navigation__mobile__mainContainer__mainList__trigger:focus {
cursor: pointer;
}
.n-navigation__mobile__mainContainer__mainList__trigger:hover .n-navigation__mobile__mainContainer__mainList__trigger__icon, .n-navigation__mobile__mainContainer__mainList__trigger:focus .n-navigation__mobile__mainContainer__mainList__trigger__icon {
color: #c41320;
}
.n-navigation__mobile__mainContainer__mainList__trigger__copy {
font-family: tahoma;
font-size: 1.625rem;
}
.n-navigation__mobile__mainContainer__mainList__trigger__icon {
font-size: 1.5em;
transition: color 350ms;
}
.n-navigation__mobile__mainContainer__mainList__panel {
width: 100%;
position: absolute;
top: 0;
left: 0;
transform: translateX(-20em);
transition: transform 350ms;
visibility: hidden;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 350ms;
}
.n-navigation__mobile__mainContainer__mainList__panel__subContainer {
padding: 3em 1.5em;
}
.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel {
width: 100%;
position: absolute;
top: 0;
left: 0;
transform: translateX(-20em);
visibility: hidden;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 350ms;
}
.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel__container {
padding: 3em 1.5em;
}
.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel--active {
visibility: visible;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 0s;
}
.n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel--right {
transform: translateX(20em);
visibility: hidden;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 350ms;
}
.n-navigation__mobile__mainContainer__mainList__panel--right {
transform: translateX(0);
}
.n-navigation__mobile__mainContainer__mainList__panel--active {
visibility: visible;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 0s;
}
.n-navigation__mobile__mainContainer--right {
transform: translateX(20em);
visibility: hidden;
transition: transform 350ms, visibility 0s;
transition-delay: 0s, 350ms;
}
.n-navigation__desktop {
display: none;
}
@media screen and (min-width: 50em) {
.n-navigation__desktop {
display: block;
}
}
.n-navigation__blur {
display: block;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: rgba(18, 18, 18, 0.36);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
}
.n-navigation__blur--hide {
opacity: 0;
visibility: hidden;
z-index: -1;
transition: opacity 450ms, visibility 0s, z-index 0s;
transition-delay: 0s, 450ms, 450ms;
}
.n-navigation__blur--show {
opacity: 1;
visibility: visible;
z-index: 0;
}
.n-navigation--active .n-navigation__mobile {
transform: translateX(0);
visibility: visible;
z-index: 2;
transition: transform 450ms, visibility 0s;
transition-delay: 0s, 0s;
}
<nav class="n-navigation" role="navigation">
<button class="n-navigation__hamburgerMenu" aria-label="Menu" aria-expanded="false">
<svg aria-hidden="true" viewBox="0 0 24 24" role="img" width="24px" height="24px" fill="none">
<path stroke="currentColor" stroke-width="1.5" d="M21 5.25H3M21 12H3m18 6.75H3"></path>
</svg>
</button>
<div class="n-navigation__logo">
<a href="#">Site Name</a>
</div>
<div class="n-navigation__mobile">
<div class="n-navigation__mobile__mainContainer">
<button class="n-navigation__mobile__mainContainer__close">X</button>
<ul class="n-navigation__mobile__mainContainer__mainList">
<li>
<button class="n-navigation__mobile__mainContainer__mainList__trigger">
<span class="n-navigation__mobile__mainContainer__mainList__trigger__copy">Menu Item 1</span>
<span class="n-navigation__mobile__mainContainer__mainList__trigger__icon"> > </span>
</button>
<div class="n-navigation__mobile__mainContainer__mainList__panel">
<div class="n-navigation__mobile__mainContainer__mainList__panel__subContainer">
<button class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__previous">All</button>
<h2>Menu Item 1</h2>
<ul class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList">
<li>
<a href="#" class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__trigger">
<span>Menu Item 1's</span>
</a>
</li>
<li>
<a href="#" class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__trigger">
<span>Menu Item 1's</span>
</a>
</li>
<li>
<button class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__trigger">
<span>Menu Item 1's</span>
<span> > </span>
</button>
<div class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel">
<div class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel__container">
<button class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__subList__panel__container__previous">Menu Item 1's</button>
<h3>Menu Item 1's</h3>
<ul>
<li>
<a href="#">HiM.\\</a>
</li>
<li>
<a href="#">Herrr</a>
</li>
</ul>
</div>
</div>
</li>
</ul>
</div>
</div>
</li>
<li>
<button class="n-navigation__mobile__mainContainer__mainList__trigger">
<span class="n-navigation__mobile__mainContainer__mainList__trigger__copy">Menu Item 2</span>
<span class="n-navigation__mobile__mainContainer__mainList__trigger__icon"> > </s>
</button>
<div class="n-navigation__mobile__mainContainer__mainList__panel">
<div class="n-navigation__mobile__mainContainer__mainList__panel__subContainer">
<button class="n-navigation__mobile__mainContainer__mainList__panel__subContainer__previous">All</button>
<h2>Menu Item 2</h2>
<ul>
<li>Menu Item 2's</li>
</ul>
</div>
</div>
</li>
<li>
<a href="#" class="n-navigation__mobile__mainContainer__mainList__trigger">
<span class="n-navigation__mobile__mainContainer__mainList__trigger__copy">Menu Item 3</span>
</a>
</li>
</ul>
</div>
</div>
<div class="n-navigation__desktop"></div>
<div class="n-navigation__iconMenu">
search and cart
</div>
<div class="n-navigation__blur n-navigation__blur--hide"></div>
</nav>
你可以试试这个
将
document.querySelector('.n-navigation__mobile__mainContainer__mainList__trigger').focus();
添加到您的 openHamburgerMenu()
函数
通过执行此操作,您可以使用 Tab 键切换到菜单项选项,您可以对其进行修改以包括处理向上和向下箭头
function openHamburgerMenu() {
navigation.classList.add("n-navigation--active");
navigationBlur.classList.remove("n-navigation__blur--hide");
navigationBlur.classList.add("n-navigation__blur--show");
this.setAttribute("aria-expanded", "true");
if (!focusableMap.has(mainContainer.id)) {
let container = mainContainer.getAttribute('class');
addFocusableToMap(mainContainer, container);
}
document.querySelector('.n-navigation__mobile__mainContainer__mainList__trigger').focus();
}