Flex Panels Gallery

Aquí te traigo un recurso genial para una galería de imágenes, está sacado del curso de #Javascript30 del cuál ya he hablado en otras ocasiones. Voy a destriparlo un poco y ver las cosas que considero más importantes, pero antes que nada vamos a ver el resultado final.

¿Bonito, verdad? Si te gusta puedes seguir leyendo y descubrir cómo funciona. 🤗

TL;DR;

NOTA: Por legibilidad del post y para que puedas hacer scroll rápidamente y ver qué te estoy contando en el resultado final, voy a acotar el código quitando todas aquellas propiedades o elementos que considero triviales y que puedes ver en el Codepen arriba de este texto 👆.

Para empezar como siempre, una de las cosas más importantes y que te facilitará las cosas muchísimo a la hora de maquetar es tener una buena estructura en el HTML por muy simple que sea. Primero tenemos un div como contenedor principal, con la clase .panels, dentro de este un div por cada panel mostrado con la clase genérica .panel, y una clase especifica para cada panel .panel1. Después, dentro de cada uno de estos paneles tenemos el texto formado por tres párrafos, el de en medio que estará fijo y se mostrará siempre, y uno arriba y abajo de este, que se mostrarán cuando se expanda el panel (hagamos click sobre él).

<div class="panels">  
  ...
  <div class="panel panel1">
    <p>Texto que aparecerá desde arriba al expandir este panel</p>
    <p>Texto central siempre visible</p>
    <p>Texto que aparecerá desde abajo al expandir este panel</p>
  </div>
  ...
</div>  

Hasta aquí todo muy sencillo, vamos a echar un ojo al CSS, primero la clase .panel.

.panel {
  ...
  box-shadow:inset 0 0 0 5px rgba(255,255,255,0.1);
  transition:
    font-size 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),
    flex 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11);
  ...
  flex:1;
  display: flex;
  flex-direction: column;
}

Aquí podemos destacar la propiedad box-shadow:inset 0 0 0 5px rgba(255,255,255,0.1); con la que se consigue ese borde transparente en los paneles, que no es más que una sombra blanca del mismo grosor en los dos ejes. También tenemos transition: font-size: ... flex: ... transiciones definidas para cuando se despliega un panel, aumentando la letra, y el tamaño del panel respecto a los otros (aumentando el valor de la propiedad 'flex'). Con la función cúbica bézier que describe la velocidad de animación cubic-bezier(0.61,-0.19, 0.7,-0.11) conseguimos ese efecto tan molón en el cuál tanto la letra como el panel se encogen antes de estirarse. Aquí puedes ver mejor la forma de la curva y compararla con los valores por defecto que tienes en las transiciones.

Sigamos, vamos con los párrafos que contienen el texto:

.panel > * {
  ...
  transition:transform 0.5s;
  flex: 1 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
}
.panel p {
  ...
  text-shadow:0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);  
}

Aquí nos encontramos con otra transición transition: transform 0.5s está será la encargada de animar la entrada de los textos tanto desde arriba como desde abajo con una duración de medio segundo. Con flex: 1 0 auto;, shorthand para las propiedades 'flex-grow', 'flex-shrink' y 'flex-basis', distribuimos de forma proporcional el espacio entre los distintos parrafos, desactivamos el encogimiento y definimos el tamaño de estos de forma automática. También hacemos que los párrafos se comporten cómo cajas flexibles para centrar su texto en los dos ejes de forma rápida y sencilla (Flexbox rules). Por último aplicamos dos sombras negras transparentes al texto con text-shadow:0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45); para dar ese efecto de profundidad en el texto y resaltarlo de manera más elegante.

Por último, las clases que aplicarán cambios en las propiedades definidas y las cuales se aplicarán una vez desplegado un panel (mediante Javascript) .panel.open y .panel.open-activate,

.panel.open {
  flex: 5;
  font-size:40px;
}
.panel > *:first-child { transform: translateY(-100%); }
.panel > *:last-child { transform: translateY(100%); }
.panel.open-active > *:first-child { transform: translateY(0); }
.panel.open-active > *:last-child { transform: translateY(0); }

Cuando apliquemos .open aumentamos el tamaño del panel flex:5 (reclamando más espacio respecto a los paneles hermanos, de ahí que estos se encojan) y también aumentamos el tamaño de la letra.

Con las líneas cinco y seis, ocultamos los párrafos primero y último en el eje vertical, mediante la propiedad transform: translateY(-100%);. Al aplicar la clase .open-activate se recuperan los dos párrafos ocultos a su posición original.

Para terminar, el código Javascript muy facilito (que he acortado de la versión original para hacerlo menos verboso):

const panels = document.querySelectorAll('.panel');  
panels.forEach(panel => panel.addEventListener('click', () => {  
  panel.classList.toggle('open');
}));
panels.forEach(panel => panel.addEventListener('transitionend', (e) =>  {  
  e.propertyName.includes('flex') && panel.classList.toggle('open-active');
}));

Como apunte la clase .open se desplegará cuando demos un click sobre cualquiera de los paneles, sin embargo las clase .open-activate solamente se aplicará cuando la transición de la propiedad 'flex' haya terminado, esto se realiza escuchando el evento 'transitionend' de cada uno de los paneles y solamente aplicando la clase cuando el evento terminado contenga la palabra 'flex', (para Safari es 'flex', mientras que para Chrome y FF es 'flex-grow'). Este es un recurso que se usa mucho para separar las transiciones y así dar un efecto con más recorrido a la transición.

Con esto ya tendríamos una panel bastante llamativo y sencillo que no requiere unos profundos conocimientos de CSS, todas las tecnologías usadas son ampliamente soportadas por todos los navegadores posteriores a IE10 (of course).

Cómo recomendación te aconsejo seguir a @wesbos en Twitter y estar atentos a cuando saque su curso gratuito #css30 que si es la mitad de bueno que el de Javascript será un must en el desarrollo de CSS.

Un saludo y espero que te sirva este recurso! 😜