Erstellung einer animierten Menüanzeige mit CSS-Selektoren

Avatar of James Nowland
James Nowland am

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

Der folgende Artikel stammt von James Nowland, einem Frontend-Entwickler bei Headjam, einer Kreativagentur in Newcastle, Australien. James hat hier einen ziemlich einfachen kleinen Effekt erzielt, der aber vielleicht den Einsatz von etwas JavaScript erfordern würde. Stattdessen nutzt er einige clevere Selektoren.

In diesem Artikel werde ich kreative Wege aufzeigen, wie Geschwister-Selektoren und Pseudo-Elemente verwendet werden können, um eine reine CSS-Menüanzeige zu erstellen, die normalerweise mit JavaScript realisiert werden würde.

Hier ist, was wir machen werden

Siehe den Pen Schritt 3 von CSS-Tricks (@css-tricks) auf CodePen.

Wir werden dies in drei Schritte unterteilen

  • Grundlegende Struktur und Styling
  • Aufbau der Anzeige
  • Bewegung der Anzeige

Wir werden auch SCSS verwenden, um von den Variablen und Funktionen zu profitieren, die Sass bietet und die die Dinge auf lange Sicht deutlich einfacher zu warten machen.

Schritt 1: Grundlegende Struktur und Styling

Zuerst richten wir das HTML für das Menü mit einer einfachen ungeordneten Listenstruktur ein. Wir können auch die grundlegenden Klassennamen markieren, um loszulegen.

<ul class="PrimaryNav">
  <li class="Nav-item">Home</li>
  <li class="Nav-item">About</li>
  <li class="Nav-item is-active">Writing</li>
  <li class="Nav-item">Clients</li>
  <li class="Nav-item">Contact</li>
</ul>

Bisher nichts Besonderes. Wir haben das <ul>-Element mit dem Klassennamen PrimaryNav, das als Container für die darin enthaltenen Listenelemente dient, die jeweils die Klasse Nav-item haben.

Definition der Variablen

Eines der Hauptmerkmale dieser Navigation ist eine maximale Breite, die den Raum eines Containers basierend auf der Anzahl der darin enthaltenen Menüeinträge füllt. In diesem Fall legen wir in unserem SCSS eine Variable $menu-items fest, die dann zur Berechnung des $width-Werts jedes .Nav-item im Markup verwendet wird.

Wir haben auch eine Variable $indicator-color hinzugefügt, um – Sie ahnen es – die Farbe zu definieren, die für die Hover-Anzeige des Menüs verwendet wird.

// Menu Item Variables
// The number of items in the menu
$menu-items: 5;
// We multiply it by 1% to get the correct % unit
$width: (100/$menu-items) * 1%;

// Colors
$background-color: #121212;
$indicator-color: #e82d00;

Styling der Elemente

Von hier aus können wir die grundlegenden Stile für das Menü erstellen

// The parent container
.PrimaryNav {
  // Remove the bullet points by default
  list-style: none;
  // Center all the things!
  margin: 50px auto;
  // The nav will never exceed this width and what our calculated percentages related back to 
  max-width: 720px;
  padding: 0;
  width: 100%;
}

// The menu items
.Nav-item {
  background: #fff;
  display: block;
  float: left;
  margin: 0;
  padding: 0;
  text-align: center;
  // Our current calculation of 5 items will generate 20%
  width: $width;

  // The first item in the menu
  &:first-child {
    border-radius: 3px 0 0 3px;
  }

  // The last item in the menu
  &:last-child {
    border-radius: 0 3px 3px 0;
  }

  // If the menu item is active, give it the same color as the indicator
  &.is-active a {
    color: $indicator-color;
  }

  a {
    color: $background-color;
    display: block;
    padding-top: 20px;
    padding-bottom: 20px;
    text-decoration: none;

    &:hover {
      color: $indicator-color;
    }
  }
}

Siehe den Pen Schritt 1 von CSS-Tricks (@css-tricks) auf CodePen.

Schritt 2: Aufbau der Anzeige

Wir werden dies so markieren, dass mehrere Klassen verwendet werden. Wir könnten dasselbe mit nur der Klasse .PrimaryNav erreichen, aber das Hinzufügen einer weiteren Klasse wird zukünftig mehr Flexibilität ermöglichen.

Wir haben bereits die Klasse .PrimaryNav, die das Hauptnavigationsstyling enthält. Jetzt erstellen wir .with-indicator, um die Anzeige zu erstellen.

<ul class="PrimaryNav with-indicator">

</ul>

Hier können wir CSS anstelle dessen verwenden, was wir normalerweise in JavaScript erledigen würden. Wir wissen, dass das Hinzufügen einer Klasse zu einem Element beim Hovern in den Bereich von JavaScript fällt, aber sehen wir, wie wir das allein mit CSS machen können.

Das Schwierige ist, die Menüeinträge miteinander kommunizieren zu lassen. In einer ungeordneten Liste kann das erste Listenelement (:first-child) mit dem zweiten Kind über den Geschwister-Selektor + oder ~ sprechen, aber das zweite Listenelement kann nicht mit dem ersten Kind sprechen (in CSS kann man nicht rückwärts im DOM navigieren).

Siehe den Pen Schritt 2 von CSS-Tricks (@css-tricks) auf CodePen.

Es stellt sich heraus, dass der beste Listener unter den Listenelementen das :last-child ist. Das letzte Kind kann **alle** :hover- und :active-Zustände seiner Geschwister hören. Das macht es zum perfekten Kandidaten, um die Anzeige zu platzieren.

Wir erstellen die rote Anzeige mit den Elementen :before und :after des letzten Kindes. Das Element :before verwendet ein CSS-Dreieck und einen negativen Rand, um es zu zentrieren.

// The hover indicator
.with-indicator {
  // The menu is "relative" to the absolute position last-child pseudo elements.
  position: relative;

.Nav-item:last-child {
  &:before, &:after {
    content: '';
    display: block;
    position: absolute;
  }
  
  // The CSS Triangle
  &:before {
    width: 0;
    height: 0;
    border: 6px solid transparent;
    border-top-color: $color-indicator;
    top: 0;
    left: 12.5%;
    // Fix the offset - may vary per use
    margin-left: -3px;
  }

  // The block that sits behind the text
  &:after {
    width: $width;
    background: $indicator-color;
    top: -6px;
    bottom: -6px;
    left: 0;
    z-index: -1;
  }
}
}

Schritt 3: Bewegung der Anzeige

Nun, da die Anzeige eingerichtet ist, muss sie sich bewegen können, wenn der Cursor über Menüeinträgen schwebt. Hier zeigt sich die Kraft des ~-Selektors, der verwendet wird, um alle Elemente zwischen dem ersten und dem letzten Kind im Markup abzugleichen.

Momentan ist position:relative standardmäßig auf das <ul>-Element gesetzt, was bedeutet, dass die Anzeige bündig mit dem ersten Element sitzt. Wir können die Anzeige von Element zu Element verschieben, indem wir die left-Position ändern und – da alle Menüs die gleiche Breite haben – wissen wir, dass wir, um sie um eine Stelle nach unten zu verschieben, die :last-child-Selektoren für :before und :after einen Versatz haben müssen, der der Breite eines .Nav-item entspricht. Erinnern Sie sich an unsere praktische $width-Variable? Die können wir auch für das left-Attribut verwenden.

So würden wir das in vanilla CSS einrichten

.with-indicator .Nav-item:nth-child(1).is-active ~ .Nav-item:last-child:after {
  left: 0;
}
.with-indicator .Nav-item:nth-child(2).is-active ~ .Nav-item:last-child:after {
  left: 20%;
}
.with-indicator .Nav-item:nth-child(3).is-active ~ .Nav-item:last-child:after {
  left: 40%;
}
.with-indicator .Nav-item:nth-child(4).is-active:after {
  left: 60%;
}
.with-indicator .Nav-item:nth-child(5).is-active:after {
  left: 80%;
}

Machen wir es dynamisch mit Sass

// Menu Item Variables
// The number of items in the menu, plus one for offset
$menu-items: 5;
// The actual number of items in the menu
$menu-items-loop-offset: $menu-items - 1;
// We multiply it by 1% to get the correct % unit
$width: (100/$menu-items) * 1%;

.with-indicator {
  @for $i from 1 through $menu-items-loop-offset {
    // When the .Nav-item is active, make the indicator line up with the navigation item.
    .Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:after {
      left:($width*$i)-$width;
    }
    
   .Nav-item:nth-child(#{$i}).is-active ~ .Nav-item:last-child:before {
      left:($width*$i)+($width/2)-$width; /* this ensures the triangle lines up to the menu. */
    }
  } // end @for loop

Es ist erwähnenswert, dass das Dreieck :before zusätzlich zu diesem left-Offset einen halben Breiten-Offset hat.

Fügen wir nun eine Animation und eine weitere Sass for-Schleife hinzu, damit wir initialisieren können, wo sich die Anzeige befindet, basierend auf der aktuellen Seite. Wenn Sie mit der Maus über das Element fahren, bewegt sich die Anzeige. Sobald Sie aber mit der Maus herausfahren, kehrt sie zum Zustand is-active zurück. Eine schöne und saubere Möglichkeit, eine Menüanzeige ohne JavaScript zu erstellen.

// We had to use !important to make the hovers overide for when the :last-child is-active or hovered
@for $i from 1 through $menu-items-loop-offset {
  // When the menu is :hover make the indicator line up with it.
  .Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:after {
    left:($width*$i)-$width !important;
  }

  .Nav-item:nth-child(#{$i}):hover ~ .Nav-item:last-child:before{
    left:($width*$i)+($width/2)-$width !important;
  }
} // end @for loop

// make sure the last-child talks to itself
.Nav-item {
  &:last-child {
    &:hover, &.is-active {
      &:before {
        left: (100%-$width)+($width/2) !important;
      }
      &:after{
        left: 100%-$width !important;
      }
    }        
  }
}

Das Endergebnis

Und da haben wir es! Eine animierte Menüanzeige ohne JavaScript-Abhängigkeit.

Siehe den Pen Schritt 3 von CSS-Tricks (@css-tricks) auf CodePen.