Botones fantasma con conciencia direccional en CSS | Programar Plus

Me sorprendería si nunca te encontraras con un botón fantasma 👻. Ya sabes cuáles: tienen un fondo transparente que se llena con un color sólido al pasar el mouse. Smashing Magazine tiene un artículo completo sobre la idea. En este artículo, crearemos un botón fantasma, pero esa será la parte fácil. La parte divertida y complicada será animar el relleno de ese botón fantasma de modo que el fondo se llene en la dirección desde la que pasa el cursor sobre él.

Aquí hay un iniciador básico para un botón fantasma:

Ver la pluma
Botón fantasma básico 👻 por Jhey (@ jh3y)
en CodePen.

En la mayoría de los casos, background-color tiene un transition a un color sólido. Hay diseños en los que el botón puede llenarse de izquierda a derecha, de arriba a abajo, etc., para darle un toque visual. Por ejemplo, aquí está de izquierda a derecha:

Ver la pluma
Botón fantasma de relleno direccional 👻 por Jhey (@ jh3y)
en CodePen.

Hay un detalle de UX aquí. Se siente mal si se mantiene suspendido contra el relleno. Considere este ejemplo. El botón se llena desde la izquierda mientras se desplaza desde la derecha.

Hover se siente apagado 👎

Es mejor si el botón se llena desde nuestro punto de desplazamiento inicial.

Hover se siente bien 👍

Entonces, ¿cómo podemos darle al botón conciencia direccional? Su instinto inicial podría ser buscar una solución de JavaScript, pero podemos crear algo con CSS y un poco de marcado adicional en su lugar.

Para aquellos en el campamento TL; DR, ¡aquí hay algunos botones fantasma CSS puros con conciencia direccional!

Ver la pluma
Botones fantasma de CSS puro con conciencia direccional ✨👻😎 por Jhey (@ jh3y)
en CodePen.

Construyamos esto paso a paso. Todo el código está disponible en esta colección de CodePen.

Creando una base

Comencemos creando las bases de nuestro botón fantasma. El marcado es sencillo.

<button>Boo!</button>

Nuestra implementación de CSS aprovechará las propiedades personalizadas de CSS. Éstos facilitan el mantenimiento. También facilitan la personalización a través de propiedades en línea.

button {
  --borderWidth: 5;
  --boxShadowDepth: 8;
  --buttonColor: #f00;
  --fontSize: 3;
  --horizontalPadding: 16;
  --verticalPadding: 8;

  background: transparent;
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  box-shadow: calc(var(--boxShadowDepth) * 1px) calc(var(--boxShadowDepth) * 1px) 0 #888;
  color: var(--buttonColor);
  cursor: pointer;
  font-size: calc(var(--fontSize) * 1rem);
  font-weight: bold;
  outline: transparent;
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  transition: box-shadow 0.15s ease;
}

button:hover {
  box-shadow: calc(var(--boxShadowDepth) / 2 * 1px) calc(var(--boxShadowDepth) / 2 * 1px) 0 #888;
}

button:active {
  box-shadow: 0 0 0 #888;
}

Ponerlo todo junto nos da esto:

Ver la pluma
Base Ghost Button 👻 por Jhey (@ jh3y)
en CodePen.

¡Estupendo! Tenemos un botón y un efecto de desplazamiento, pero no hay relleno que lo acompañe. Hagámoslo a continuación.

Agregar un relleno

Para hacer esto, creamos elementos que muestran el estado de relleno de nuestro botón fantasma. El truco consiste en recortar esos elementos con clip-path y esconderlos. Podemos revelarlos cuando pasamos el cursor sobre el botón cambiando el clip-path.

Elemento secundario con un clip del 50%

Ellos deber alinearse con el botón principal. Nuestras variables CSS ayudarán mucho aquí.

A primera vista, podríamos haber buscado pseudoelementos. Sin embargo, no habrá suficientes pseudoelementos para todas las direcciones. También interferirán con la accesibilidad … pero hablaremos de esto más adelante.

Comencemos agregando un relleno básico de izquierda a derecha al pasar el mouse. Primero, agreguemos un lapso. Ese lapso necesitará el mismo contenido de texto que el botón.

<button>Boo!
  <span>Boo!</span>
</button>

Ahora tenemos que alinear nuestro intervalo con el botón. Nuestras variables CSS harán el trabajo pesado aquí.

button span {
  background: var(--buttonColor);
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  bottom: calc(var(--borderWidth) * -1px);
  color: var(--bg, #fafafa);
  left: calc(var(--borderWidth) * -1px);
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
}

Finalmente, recortamos el tramo fuera de la vista y agregamos una regla que lo revelará al pasar el mouse actualizando el clip. Definir una transición le dará esa guinda.

button span {
  --clip: inset(0 100% 0 0);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  transition: clip-path 0.25s ease, -webkit-clip-path 0.25s ease;
  // ...Remaining div styles
}

button:hover span {
  --clip: inset(0 0 0 0);
}

Ver la pluma
Botón fantasma con relleno LTR 👻 por Jhey (@ jh3y)
en CodePen.

Añadiendo conciencia direccional

Entonces, ¿cómo podríamos agregar conciencia direccional? Necesitamos cuatro elementos. Cada elemento será responsable de detectar un punto de entrada flotante. Con clip-path, podemos dividir el área del botón en cuatro segmentos.

Cuatro :hover segmentos

Agreguemos cuatro tramos a un botón y colóquelos para llenar el botón.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</button>
button span {
  background: var(--bg);
  bottom: calc(var(--borderWidth) * -1px);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  left: calc(var(--borderWidth) * -1px);
  opacity: 0.5;
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
  z-index: 1;
}

Podemos apuntar a cada elemento y asignar un clip y color con variables CSS.

button span:nth-of-type(1) {
  --bg: #00f;
  --clip: polygon(0 0, 100% 0, 50% 50%, 50% 50%);
}
button span:nth-of-type(2) {
  --bg: #f00;
  --clip: polygon(100% 0, 100% 100%, 50% 50%);
}
button span:nth-of-type(3) {
  --bg: #008000;
  --clip: polygon(0 100%, 100% 100%, 50% 50%);
}
button span:nth-of-type(4) {
  --bg: #800080;
  --clip: polygon(0 0, 0 100%, 50% 50%);
}

Frio. Para probar esto, cambiemos la opacidad al pasar el mouse.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  opacity: 1;
}

Tan cerca

UH oh. Aquí hay un problema. Si ingresamos y pasamos el cursor sobre un segmento, pero luego pasamos el mouse sobre otro, la dirección de relleno cambiaría. Eso va a parecer mal. Para solucionar este problema, podemos establecer un z-index y clip-path al pasar el mouse para que un segmento llene el espacio.

button span:nth-of-type(1):hover,
button span:nth-of-type(2):hover,
button span:nth-of-type(3):hover,
button span:nth-of-type(4):hover {
  --clip: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  opacity: 1;
  z-index: 2;
}

Ver la pluma
Conciencia direccional de CSS puro con ruta de clip 👻 por Jhey (@ jh3y)
en CodePen.

Poniendolo todo junto

Sabemos cómo crear la animación de relleno y sabemos cómo detectar la dirección. ¿Cómo podemos juntar los dos? ¡Usa el combinador de hermanos!

Hacerlo significa que cuando colocamos el cursor sobre un segmento direccional, podemos revelar un elemento de relleno en particular.

Primero, actualice el marcado.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
</button>

Ahora, podemos actualizar el CSS. Refiriéndonos a nuestro relleno de izquierda a derecha, podemos reutilizar el estilo. Solo necesitamos establecer un clip-path para cada elemento. Me acerqué al pedido de la misma manera que algunos valores de propiedad. El primer niño está arriba, el segundo tiene razón, y así sucesivamente.

button b:nth-of-type(1) {
  --clip: inset(0 0 100% 0);
}
button b:nth-of-type(2) {
  --clip: inset(0 0 0 100%);
}
button b:nth-of-type(3) {
  --clip: inset(100% 0 0 0);
}
button b:nth-of-type(4) {
  --clip: inset(0 100% 0 0);
}

La última pieza es actualizar el clip-path para el elemento relevante al pasar el cursor por el segmento emparejado.

button span:nth-of-type(1):hover ~ b:nth-of-type(1),
button span:nth-of-type(2):hover ~ b:nth-of-type(2),
button span:nth-of-type(3):hover ~ b:nth-of-type(3),
button span:nth-of-type(4):hover ~ b:nth-of-type(4) {
  --clip: inset(0 0 0 0);
}

¡Tada! Tenemos un botón fantasma CSS puro con conciencia direccional.

Ver la pluma
Botón fantasma CSS puro con conciencia direccional 👻 por Jhey (@ jh3y)
en CodePen.

Accesibilidad

En su estado actual, no se puede acceder al botón.

VoiceOver lee el marcado adicional.

Esos elementos adicionales no ayudan mucho, ya que un lector de pantalla repetirá el contenido cuatro veces. Necesitamos ocultar esos elementos de un lector de pantalla.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
</button>

No más contenido repetido.

Ver la pluma
Botón fantasma CSS puro accesible con conciencia direccional 👻 por Jhey (@ jh3y)
en CodePen.

¡Eso es!

Con un poco de marcado adicional y algunos trucos CSS, podemos crear botones fantasma con conciencia direccional. Utilice un preprocesador o cree un componente en su aplicación y no necesitará escribir todo el HTML también.

Aquí hay una demostración que hace uso de variables CSS en línea para controlar el color del botón.

Ver la pluma
Botones fantasma de CSS puro con conciencia direccional ✨👻😎 por Jhey (@ jh3y)
en CodePen.