Beispiel:CSS Menu8.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche

<!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 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:

  1. HTML: verschachtelte Listen mit bis zu drei Ebenen ohne Klassen
  2. CSS: Auf schmalen Viewports vertikal – auf genügend breiten Viewports horizontal angeordnet.
  3. 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ü.

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>