Zielgerichtete Menüelemente mit Untermenüs in einer Navigationsleiste

Avatar of Ray Messina
Ray Messina am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Der folgende Text ist ein Gastbeitrag von Ray Messina. Ray war daran interessiert, diese Technik als Möglichkeit, Dinge, die er von dieser Seite gelernt hat, zurückzugeben, zu teilen, was großartig ist. Sie kennen vielleicht die jQuery-Methode .has, mit der Sie ein Element auswählen können, wenn es einen anderen Selektor als Nachfahren enthält. Eine solche Auswahl gibt es in CSS leider noch nicht. Aber wenn Sie ein wenig über die HTML-Struktur wissen, können Sie eine Kombination von Positionsselektoren verwenden, um sie nachzuahmen. Ray wird es erklären.

Kürzlich habe ich an einer Dropdown-Navigationsleiste gearbeitet und wollte die Menüpunkte, die Untermenüs enthielten, von denen, die keine enthielten, unterscheiden. Ich wollte dies automatisch tun können, ohne auf JavaScript angewiesen zu sein oder Klassen zum Markup hinzufügen zu müssen. Ich wollte einfach, dass es so funktioniert, als ob der Punkt gerade *wüsste*, ob er ein Untermenü hatte oder nicht.

Der Bedarf an Navigationshinweisen

Wie Sie wahrscheinlich wissen, sind Menüs Listen von Links und daher ist es üblich, sie als <ul>s zu kennzeichnen. Im weiteren Sinne sind Dropdown-Menüs lediglich verschachtelte <ul>s. Dropdowns sind eine gängige Komponente des modernen (und nicht so modernen) Webdesigns. Mit reinem CSS kann man die obere Ebene eines Navigationsmenüs beliebig gestalten und die Unterebenen ausblenden, sodass sie erst dann sichtbar werden, wenn der Besucher mit der Maus über den entsprechenden Bereich fährt.

Viele Designer sind damit zufrieden. Aus UX/UI-Sicht mangelt es jedoch daran, da der Benutzer das gesamte Menü erkunden muss, um herauszufinden, welche Abschnitte zusätzliche Navigationslinks enthalten. Das bedeutet, dass Besucher entweder Zeit verschwenden oder Bereiche Ihrer Website ganz verpassen! Das ist gar nicht gut.

Der übliche Weg, dieses Problem zu lösen, besteht darin, den <li>s, die <ul>s (Untermenüs) enthalten, einfach eine Klasse zuzuweisen, damit wir diese Elemente anders gestalten können als diejenigen, die keine Unter Navigation enthalten. Ziemlich einfach, aber auch ziemlich mühsam und nicht besonders elegant.

Eine andere Möglichkeit, vorausgesetzt, die Elemente der oberen Ebene sind *nicht* als Links codiert, besteht darin, *diesen Unterschied in den Tags als Hebel* zu nutzen. Das heißt, Pseudo-Links erstellen: <span>s gestalten, die direkte Kinder der <li> sind, um anzuzeigen, dass weitere Links folgen, und im Gegensatz dazu Anker-Tags so gestalten, dass sie keine zusätzlichen Unterelemente haben. Dies ist praktisch, wenn Ihr Dropdown einfach ist und Sie es speziell so strukturieren können. Es ist sauberer, da Sie keine „submenu“-Klassen zu Ihrem Markup hinzufügen müssen, aber wie zuvor erfordert es, dass Sie den Inhalt Ihrer Liste manuell strukturieren oder dass Ihr CMS vorhersagen kann, welche Listenelemente andere <ul>s als Untermenüs enthalten werden.

Automatisch machen!

Ich bin auf andere Methoden gestoßen, um Listenelemente, die andere Listen enthalten, automatisch zu stylen, aber sie *verwendeten absolute Positionierung und ein Pseudo-Element vom untergeordneten <ul>*. Während clever, können die CSS-Berechnungen manchmal knifflig sein oder die Methode ist je nach den Techniken, die Sie zum Layout Ihres Menüs und/oder zur Positionierung Ihrer Untermenüs verwendet haben, oder dem gewünschten Gesamteffekt möglicherweise gar nicht umsetzbar.

Ein besserer Weg

Idealerweise wäre es großartig, wenn es einen CSS-Selektor gäbe, mit dem wir abfragen könnten, ob ein Element ein anderes Element als direktes Kind enthält, so ähnlich wie die .has()-Methode von jQuery.

Wir können fast dasselbe mit li a:first-child:nth-last-child(x) { } erreichen.

Der Schlüssel ist eine erwartete Kindanzahl (HTML-Element geplante Elternschaft?). Wahrscheinlich werden es zwei Elemente sein: der Anker und das <ul>, obwohl man diese Technik anpassen kann, um für jede beliebige Anzahl oder Kindelemente zu funktionieren, solange Sie ein regelmäßiges Muster haben.

Hier ist ein kurzes Beispiel. Das Markup ist nur Ihr Standard-verschachteltes UL, aber beachten Sie, dass ich **nur eine Klasse im HTML** auf dem Stamm-<ul> verwendet habe. Testen Sie dies selbst, fügen Sie beliebige verschachtelte Listen auf jeder Ebene hinzu!

Erstellen einer Dropdown-Demo

Lassen Sie uns diese Idee auf die Probe stellen!

Das HTML: Sauber halten

<nav>
  <ul class="nav">
    <li><a href="#">About</a></li>
    <li><a href="#">Portfolio</a>
      <ul>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Resume</a>
      <ul>
        <li><a href="#">item a lonng submenu</a></li>
        <li><a href="#">item</a>
          <ul>
            <li><a href="#">Ray</a></li>
            <li><a href="#">Veronica</a></li>
            <li><a href="#">Bushy</a></li>
            <li><a href="#">Havoc</a></li>
          </ul>
        </li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Download</a></li>
    <li><a href="#">Rants</a>
      <ul>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
        <li><a href="#">item</a></li>
      </ul>
    </li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

Das CSS

Einige allgemeine Stile, um es schick zu machen

nav {    
  display: block;
  text-align: center;
}
nav ul {
  margin: 0;
  padding:0;
  list-style: none;
}
.nav a {
  display:block; 
  background: #111; 
  color: #fff; 
  text-decoration: none;
  padding: 0.8em 1.8em;
  text-transform: uppercase;
  font-size: 80%;
  letter-spacing: 2px;
  text-shadow: 0 -1px 0 #000;
  position: relative;
}
.nav{  
  vertical-align: top; 
  display: inline-block;
  box-shadow: 
    1px -1px -1px 1px #000, 
    -1px 1px -1px 1px #fff, 
    0 0 6px 3px #fff;
  border-radius:6px;
}
.nav li {
  position: relative;
}
.nav > li { 
  float: left; 
  border-bottom: 4px #aaa solid; 
  margin-right: 1px; 
} 
.nav > li > a { 
  margin-bottom: 1px;
  box-shadow: inset 0 2em .33em -0.5em #555; 
}
.nav > li:hover, 
.nav > li:hover > a { 
  border-bottom-color: orange;
}
.nav li:hover > a { 
  color:orange; 
}
.nav > li:first-child { 
  border-radius: 4px 0 0 4px;
} 
.nav > li:first-child > a { 
  border-radius: 4px 0 0 0;
}
.nav > li:last-child { 
  border-radius: 0 0 4px 0; 
  margin-right: 0;
} 
.nav > li:last-child > a { 
  border-radius: 0 4px 0 0;
}
.nav li li a { 
  margin-top: 1px;
}

Dann **passiert die Magie**

.nav li a:first-child:nth-last-child(2):before { 
  content: ""; 
  position: absolute; 
  height: 0; 
  width: 0; 
  border: 5px solid transparent; 
  top: 50% ;
  right:5px;  
 }

Das ist im Wesentlichen der aktive Bestandteil dieser Technik. Für dieses Beispiel habe ich das :before Pseudo-Element des Anker-Elements verwendet, um die Pfeile zu zeichnen. Das Pseudo-Element ist für die Technik *nicht notwendig*. Ich hätte genauso gut Hintergründe auf dem Anker selbst ändern können; Sie können fast alles damit machen, sobald Sie das Element ausgewählt haben.

Schließlich noch ein wenig CSS-Code für Positionierung und Pfeilstile, um es abzurunden.

/* submenu positioning*/
.nav ul {
  position: absolute;
  white-space: nowrap;
  border-bottom: 5px solid  orange;
  z-index: 1;
  left: -99999em;
}
.nav > li:hover > ul {
  left: auto;
  margin-top: 5px;
  min-width: 100%;
}
.nav > li li:hover > ul { 
  left: 100%;
  margin-left: 1px;
  top: -1px;
}
/* arrow hover styling */
.nav > li > a:first-child:nth-last-child(2):before { 
  border-top-color: #aaa; 
}
.nav > li:hover > a:first-child:nth-last-child(2):before {
  border: 5px solid transparent; 
  border-bottom-color: orange; 
  margin-top:-5px
}
.nav li li > a:first-child:nth-last-child(2):before {  
  border-left-color: #aaa; 
  margin-top: -5px
}
.nav li li:hover > a:first-child:nth-last-child(2):before {
  border: 5px solid transparent; 
  border-right-color: orange;
  right: 10px; 
}

Hier ist das Ganze in Aktion bei CodePen

Wie Sie wahrscheinlich vermutet haben, könnten Sie auch andere Selektor/Selektor-Kombinationen wie :only-child, :first-child:last-child, :first-child:not(:last-child) und ähnliches verwenden. Aber ich habe festgestellt, dass :nth-child(x):nth-last-child(x) **die größte Flexibilität bietet** und als integrierte Fallback-Lösung dient (da es uns ermöglicht, das Element direkt und nicht durch Ausschluss anzusprechen), und es gibt wenig Cross-Browser-Vorteile, wenn man die anderen Selektor/Selektor-Kombinationen verwendet.

Das war's. Einfach, elegant und völlig automatisch; so wie es sein sollte. Die Unterstützung dafür ist fast universell, außer bei IE, das Klebstoff frisst und nur ab IE9+ Unterstützung bietet. Zum Zeitpunkt des Schreibens lag die geschätzte globale Unterstützung für diese Selektoren in der verwendeten Form bei etwa 87%, was nicht schlecht ist.