Beispiel:CSS Menu8.html
Aus SELFHTML-Wiki
<!DOCTYPE html> <html lang="de"> <head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menü 8: Buttons erzeugen und Submenüs verstecken</title>
<link rel="stylesheet" media="screen" href="./Beispiel:Grundlayout.css">
<script>
document.addEventListener('DOMContentLoaded', function () {
initDropdownNavigation();
function initDropdownNavigation() {
document.querySelectorAll('nav').forEach((nav, navIndex) => {
nav.querySelectorAll('li > ul').forEach((submenu, submenuIndex) => {
const submenuId = `submenu-${navIndex + 1}-${submenuIndex + 1}`;
submenu.id = submenu.id || submenuId;
submenu.insertAdjacentHTML('beforebegin', `
<button type="button" aria-expanded="false" aria-controls="${submenu.id}">
Untermenü auf- und zuklappen
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1">
<path d="M0.3,0.1 0.3,0.9 0.8,0.5z"></path>
</svg>
</button>
`);
});
nav.addEventListener('click', event => {
const button = event.target.closest('button[aria-expanded]');
if (!button || !nav.contains(button)) {
return;
}
toggleSubmenu(button);
});
nav.addEventListener('keydown', event => {
if (event.key === 'Escape') {
closeSubmenus(nav);
return;
}
const button = event.target.closest('button[aria-expanded]');
if (button && nav.contains(button) && (event.key === 'Enter' || event.key === ' ')) {
event.preventDefault();
toggleSubmenu(button);
}
});
nav.addEventListener('focusin', event => {
closeSubmenus(nav, event.target);
});
nav.addEventListener('focusout', event => {
if (!nav.contains(event.relatedTarget)) {
closeSubmenus(nav);
}
});
});
}
function toggleSubmenu(button) {
const isOpen = button.getAttribute('aria-expanded') === 'true';
setSubmenuState(button, !isOpen); }
function setSubmenuState(button, isOpen) {
const submenu = document.getElementById(button.getAttribute('aria-controls'));
if (!isOpen) {
closeSubmenus(submenu);
}
button.setAttribute('aria-expanded', String(isOpen));
submenu.classList.toggle('visible', isOpen);
}
function closeSubmenus(root, keepTarget = null) {
const buttons = root.querySelectorAll('button[aria-expanded="true"]');
buttons.forEach(button => {
if (!keepTarget || !button.closest('li').contains(keepTarget)) {
setSubmenuState(button, false);
}
});
}
});
</script>
<style>
:root {
--background-color: midnightblue;
--accent1-color: gold;
--accent2-color: darkred;
}
nav > ul {
list-style: none;
/* Safari/VoiceOver-Fix, siehe https://www.scottohara.me/blog/2019/01/12/lists-and-safari.html */
list-style: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E");
display: flex;
flex-direction: column;
margin: 0;
padding: 0;
}
nav li {
flex: 1 1 0%;
font-size: 1.3rem;
margin: 0.2em;
padding: 0;
position: relative;
}
nav a {
display: block;
padding: 0.6em 0.2em;
text-decoration: none;
font-weight: bold;
text-align: center;
border: thin solid var(--background-color);
border-radius: 0.5em;
color: var(--accent1-color);
background-color: var(--background-color);
transition: color 0.25s ease-in, background-color 0.25s ease-in;
}
nav a[aria-current] {
color: var(--accent1-color);
background-color: var(--accent2-color);
}
nav a:focus,
nav a:hover,
nav a[aria-current]:focus,
nav a[aria-current]:hover {
color: var(--background-color);
background-color: var(--accent1-color);
}
/* submenu navigation links */
nav ul ul {
visibility: hidden;
height: 0;
overflow: hidden;
padding-left: 0.6em;
}
/* CSS-only-Variante zu Demonstrationszwecken deaktiviert */
/*
nav li:hover > ul {
visibility: visible;
height: auto;
}
nav li:focus-within > ul,
*/
.visible {
visibility: visible;
height: auto;
overflow: visible;
}
nav button[aria-expanded] {
position: absolute;
right: 0.5em;
top: 0.25em;
width: 3.7em;
height: 3.7em;
padding: 0;
color: var(--accent1-color);
background: var(--background-color); /* damit er bei nav a:focus noch sichtbar ist */
border: thin solid var(--background-color);
border-radius: 0.3em;
}
nav button[aria-expanded]:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
nav button[aria-expanded] svg {
pointer-events: none;
fill: var(--accent1-color);
border-radius: 0.3em;
}
nav li:hover [aria-expanded] svg {
background: var(--accent1-color);
fill: var(--background-color);
}
nav button[aria-expanded]:focus svg,
nav button[aria-expanded]:hover svg {
fill: var(--background-color);
background: var(--accent1-color);
}
nav button[aria-expanded="true"] svg {
transform: translate(0, 0) rotate(90deg);
}
.visually-hidden {
position: absolute !important;
clip-path: inset(50%) !important;
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden !important;
}
@media (min-width: 45em) {
nav > ul {
flex-direction: row;
}
nav li {
flex: 1;
flex-basis: 15em;
font-size: 1em;
}
nav button[aria-expanded] {
width: 2.7em;
height: 2.7em;
}
}
kbd {
background: #f9f9f9 linear-gradient(to bottom, #eee, #f9f9f9, #eee) repeat scroll 0 0;
border: thin solid #aaa;
border-radius: 2px;
box-shadow: 1px 2px 2px #ddd;
font-family: inherit;
font-size: 0.9em;
padding: 0 0.5em;
}
.notice {
background: #fed8cd;
border: thin solid #c82f04;
color: #c82f04;
border-radius: 0.5em;
padding: 1em;
}
</style>
</head>
<body>
Menü 8: Buttons erzeugen und Submenüs verstecken
<nav aria-label="Hauptnavigation">
- <a href="#">Seite 1</a>
-
<a href="#">Seite 2</a>
-
<a href="#2a">Seite 2a</a>
- <a href="#2aI">Seite 2aI</a>
- <a href="#2aII">Seite 2aII</a>
- <a href="#2b">Seite 2b</a>
-
<a href="#2a">Seite 2a</a>
- <a aria-current="page" href="#">aktuelle Seite</a>
-
<a href="#">Seite 4</a>
- <a href="#4a">Seite 4a</a>
- <a href="#4b">Seite 4b</a>
- <a href="#4c">Seite 4c</a>
- <a href="#">Seite 5</a>
</nav>
Dieses Beispiel erweitert die CSS-basierte Variante um einige Zusatzfunktionen, die das Dropdown-Menü für alle benutzbar machen.
Ziele sind:
- HTML: verschachtelte Listen mit bis zu drei Ebenen ohne Klassen
- CSS: Auf schmalen Viewports vertikal – auf genügend breiten Viewports horizontal angeordnet.
-
JavaScript: Bedienung mit Maus, Touch und Tastatur
-
Jedes Listenelement mit einer Unterliste enthält zwei UI-Elemente:
- den eigentlichen Link auf eine Unterseite und …
- einen dynamisch erzeugten Button zum Öffnen/Schließen des Untermenüs.
- Links und Buttons können mit der Tab-Taste ↹ erreicht und dann mit ⏎ oder Space ausgewählt werden. (Shift+↹ führt zurück!)
- Esc schließt das aufgeklappte Menü.
-
Jedes Listenelement mit einer Unterliste enthält zwei UI-Elemente:
Um die Funktionsweise der Tastenbedienung zu demonstrieren, wurden die CSS-Regelsätze für li:hover ul auskommentiert. Oder klicke mit der Maus auf die Pfeil-Buttons.
</body> </html>