Creación de un indicador de menú animado con selectores CSS | Programar Plus

El siguiente artículo es de James Nowland, desarrollador front-end de Headjam, una agencia creativa en Newcastle, Australia. James ha creado un pequeño efecto bastante simple aquí, pero uno que podría pensar que requeriría un poco de JavaScript. En su lugar, utiliza un selector inteligente.

En este artículo, cubriré formas creativas de usar selectores hermanos y pseudo elementos para hacer un indicador de menú solo de CSS que normalmente se lograría usando JavaScript.

Esto es lo que haremos:

Vea el Paso 3 de Pen por Programar Plus(@ css-tricks) en CodePen.

Lo dividiremos en tres pasos:

  • Estructura y estilo básicos
  • Construyendo el indicador
  • Haciendo que el indicador se mueva

También aprovecharemos SCSS a lo largo de este ejemplo para aprovechar las variables y funciones que ofrece Sass que facilitan mucho el mantenimiento a largo plazo.

Paso 1: estructura básica y estilo

Primero que nada, configuremos el HTML para el menú usando una estructura de lista desordenada básica. También podemos marcar los nombres de las clases base para comenzar.

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

Nada demasiado lujoso hasta ahora. Tenemos el <ul> elemento con un PrimaryNav nombre de clase que actúa como contenedor de los elementos de la lista dentro de él, cada uno con un Nav-item clase.

Definiendo las variables

Una de las características clave de esta navegación es un ancho máximo que llena el espacio de un contenedor en función de la cantidad de elementos de menú que contiene. En este caso, configuraremos un $menu-items variable en nuestro SCSS que luego se utilizará para calcular el $width valor de cada uno .Nav-item en el marcado.

También hemos agregado un $indicator-color variable para definir, lo adivinó, el color que se utilizará para el indicador de desplazamiento del menú.

// 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;

Peinando las cosas

Desde aquí, podemos crear los estilos básicos para el menú:

// 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;
    }
  }
}

Consulte el Paso 1 del lápiz mediante Programar Plus(@ css-tricks) en CodePen.

Paso 2: construcción del indicador

Vamos a marcar esto de una manera que use múltiples clases. Podríamos lograr lo mismo usando solo el .PrimaryNav class, pero agregar otro nombre de clase permitirá una mayor flexibilidad en el futuro.

Ya tenemos el .PrimaryNav clase que contiene el estilo de navegación principal. Ahora creemos .with-indicator para construir el indicador:

<ul class="PrimaryNav with-indicator">

</ul>

Aquí es donde podemos usar CSS en lugar de lo que normalmente lograríamos en JavaScript. Sabemos que agregar una clase a un elemento al pasar el mouse es territorio de JavaScript, pero veamos cómo podemos hacer esto solo en CSS.

La parte complicada es lograr que los elementos del menú se comuniquen entre sí. En una lista desordenada, el primer elemento de la lista (:first-child) puede hablar con el segundo hijo a través de cualquier selector de hermanos + o ~, pero el segundo elemento de la lista de niños no puede hablar con el primer niño (no puede retroceder en el DOM como en CSS).

Vea el Paso 2 de Pen por Programar Plus(@ css-tricks) en CodePen.

Resulta que el mejor oyente de los elementos de la lista es el :last-child. El ultimo niño puede escuchar todo de El :hover y :active estados de sus hermanos. Esto lo convierte en el candidato perfecto para saber dónde colocar el indicador.

Creamos el indicador rojo usando el :before y :after elementos del último hijo. El :before utilizará un triángulo CSS y un margen negativo para centrarlo.

// 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;
  }
}
}

Paso 3: hacer que el indicador se mueva

Ahora que el indicador está configurado, debe poder moverse cuando un cursor se desplaza sobre los elementos del menú. Contempla el poder del ~ selector, que se utilizará para hacer coincidir cualquier elemento entre el primer y el último hijo en el marcado.

Ahora, position:relative se establece en el <ul> elemento de forma predeterminada, lo que significa que el indicador se encuentra alineado en el primer elemento. Podemos mover el indicador de un elemento a otro modificando el left posición y, dado que todos los menús tienen el mismo ancho, sabemos que para moverlo hacia abajo :last-child selectores para :before y :after debe tener un desplazamiento igual al ancho de un .Nav-item. Recuerda nuestro práctico $width ¿variable? Podemos usar eso para left atributo.

Así es como lo configuraríamos en CSS vainilla:

.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%;
}

Hagamos esta dinámica con 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

Vale la pena señalar el triángulo :before tiene un desplazamiento adicional de medio ancho en la parte superior de este leftcompensar.

Ahora agreguemos algo de animación y otro Sass. for bucle para que podamos inicializar donde está el indicador, según la página en la que nos encontremos. Cuando usted :hover sobre el elemento, el indicador se moverá. Pero, una vez que mueva el mouse, volverá a la is-active Expresar. Una forma agradable y ordenada sin JavaScript de hacer un indicador de menú.

// 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;
      }
    }        
  }
}

El resultado final

¡Y ahí lo tenemos! Un indicador de menú animado sin la dependencia de JavaScript.

Vea el Paso 3 de Pen por Programar Plus(@ css-tricks) en CodePen.