Flexbox: ¿vía rápida hacia el nirvana del layout?
Chris David Mills. 9 de octubre de 2012. Artículo original en inglés
Introducción
HTML y CSS constituyen un mecanismo de distribución de contenidos genial en muchos aspectos — es fácil de aprender, flexible y potente. Pero un aspecto en el que nunca se ha destacado es en el de los diseños complejos. Si desea crear un diseño tipográfico simple con una imagen flotante o dos, entonces está bien, pero la producción de complicados diseños de varias columnas ha sido siempre más incómodo y cuestión de 'hacks', y frustrante para conseguir que funcione de forma consistente y precisa sobre los distintos navegadores. Por lo general, se tienden a abusar de floats y otras construcciones para este fin, y los errores y las diferencias de renderizado pueden arruinarte la diversión.
Para combatir esto, CSS3 incluye una serie de módulos que existen para hacer diferentes tareas de diseño mucho más fáciles. Ya consideramos Multi-column layout y Generated Content for Paged Media en otros artículos, y ahora volvemos nuestra atención al Módulo de Layout de Cajas Flexibles.
Flexbox, como se le llama comúnmente, está diseñado para permitirnos manipular fácilmente el diseño de una serie de elementos hijo en formas que anteriormente eran difíciles. Por ejemplo:
- Disponer esos elementos en una fila sin tener que establecer un ancho explícitos para cada uno, y envolver los elementos secundarios si no hay espacio suficiente para que quepan en una sola línea.
- Cambiorlos deprisa para que queden dispuestos verticalmente en una columna.
- Alinearlos a la izquierda, derecha, centrados, etc, dentro del contenedor primario.
- Cambiar el orden en el que se muestran, sin alterar el marcado.
- Ajustar la cantidad de espacio que ocupa cada hijo como porcentaje de la anchura o altura del padre, sin tener que preocuparse sobre cómo especificar anchuras exactas o por si el diseño seguirá funcionando si las dimensiones principales están en porcentajes y cambia el tamaño de la ventana.
Suena bastante útil, ¿no? Explorémoslo con más detalle.
Nota
En este artículo se utiliza la última sintaxis Flexbox, actualmente soportada en Opera Mobile 12.1+, Opera 12.5+, Firefox 18+ (parcialmente) y Chrome. Chrome necesita prefijos -webkit-
, mientras que Opera ofrece soporte sin prefijos desde el principio. Firefox ofrece soporte parcial con un prefijo -moz-
, y el soporte también se oculta detrás de una opción de configuración (para activarlo, ir a about:config en la barra de direcciones, buscra "FlexBox" y hacer doble clic en layout.css.flexbox
de forma que su valor quede como true). Nótese que otros navegadores ofrecen Flexbox desde 2009, pero utilizando una sintaxis antigua, obsoleta que no debe utilizarse. Hay que tener esto en cuenta a la hora de leer y usar el código de artículos anteriores a 2012 sobre Flexbox - Chris Coyier tiene más información sobre cómo saber si estás leyendo un artículo obsoleto.
Un ejemplo simple de flex
Para comenzar la fiesta flex, vamos a considerar un ejemplo sencillo para mostrar lo fácil que es el diseño con Flexbox. Consideraremos una construcción de tipo "fat footer" con tres elementos hijos, cada uno de los cuales contiene contenido típico de un pie de página. Tendremos datos de contacto, enlaces importantes y el aviso de derechos de autor. Queremos mostrar esa información horizontalmente repartida por el pie de página, centrada verticalmente, y queremos que los enlaces tengan el doble de ancho que los otros dos hijos. Actualmente normalmente se haría esto flotando los elementos secundarios, dándoles un ancho y ajustando la alineación usando cantidades variables de relleno, etc. Esto es a menudo incómodo e impreciso si no se establecen valores fijos para todas las dimensiones, algo que puede hacer las cosas bastante inflexible. Pero Flexbox puede ayudar.
Figura 1: Mi ejemplo flexible. Todo es bastante simple, aparte del pie de página, que tiene un diseño flexible que consta de tres cajas.
Nota:
Puedes ver el ejemplo completo (Figura 1) — verás que he hecho un pequeño diseño moderno con una columna de contenido y un pie de página en la parte inferior, que se mantiene en su lugar por medio de un poco de magia position: fixed;
. La disposición es flexible, con el tamaño de la columna de la página principal como porcentaje de la anchura de la página, y los elementos secundarios del pie de página cambian de tamaño en proporción a la columna principal. También te darás cuenta que he utilizado unas cuantas media queries para cambiar el diseño para anchos de pantalla más pequeños.
Primeros pasos con Flexbox
Así que, ¿cómo empezamos con Flexbox? La mayoría de las propiedades FlexBox se aplican al contenedor principal de los elementos a los que deseas aplicar un layout. Para especificar que deseas disponer un contenedor como Flexbox, se utiliza un valor especial de la propiedad display
, de esta forma:
footer {
display: flex;
}
A continuación, puedes utilizar la propiedad flex-flow
para especificar que deseas que sus elementos hijos estén dispuestos en una row
(es el valor por defecto), o una column
. También puedes incluir la palabra clave wrap
, para especificar que deseas que el contenido salte a una nueva línea si el contenedor primario es demasiado estrecho para que todos los hijos FlexBox quepan en una línea. En mi ejemplo, he puesto los hijos del pie de página a row wrap
:
footer {
display: flex;
flex-flow: row wrap;
}
El significado de la palabra clave wrap
quedará claro más adelante.
Nota
flex-flow
es en realidad una propiedad abreviada, para las dos propiedades flex-direction
(con valores row
, column
, row-reverse
o column-reverse
; los dos últimos valores que la fila o columna corra en la dirección opuesta) y flex-wrap
(con valores, wrap
, no-wrap
o wrap-reverse
).
Eje principal, eje transversal
Un concepto al que acostumbrarse cuando se trabaja con Flexbox es el de eje principal y eje transversal, que funcionan un poco como los ejes X e Y, pero con diferencias. El eje principal siempre va en la dirección del flujo de flex, horizontalmente si los hijos flex se colocan en fila y vertical si se colocan en columna. El eje transversal es perpendicular al eje principal. Estos se ilustran en la Figura 2.
Figura 2: Una ilustración del eje principal y el eje transversal de una Flexbox.
Ajustar la alineación de los hijos FlexBox
Flexbox cuenta con un número de maneras de ayudar a alinear los hijos a lo largo del eje principal y transversal.
align-items
sobre el eje transversal
El primero que veremos es align-items
, que permite alinear los hijos FlexBox lo largo del eje transversal. Los diferentes valores son:
flex-start/baseline
: Hace que el inicio de todos los elementos esté a ras del inicio del eje transversal.flex-end
: Hace que el final de todos los artículos esté a ras de la parte inferior del eje transversal.center
: Hace que el centro de todos los artículos esté a ras de la parte central del eje transversal.stretch
: Hace que todos los elementos secundarios se expandan para llenar toda la longitud del eje transversal
Todos se explican por sí mismos. Para verlos en acción, ve a jugar con la demo, y ajusta los valores, o echa un vistazo a la Figura 3. Para el ejemplo, acabé optando por:
footer {
display: flex;
flex-flow: row wrap;
align-items: stretch;
}
Y ahí está — todos los ítems ocuparán toda la altura de su contenedor padre, sin importar lo que cambie la anchura y la altura por cambios de tamaño de la ventana. Es simplemente increíble: ¿Cuántas veces en el pasado has querido dar a un conjunto de columnas la misma altura aunque contienen una cantidad diferente de contenido, y tuviste que perder el tiempo con soluciones inflexibles como establecer una altura igual para todos ellos, o usar columnas falsas?
Figura 3: Mostrando cómo los distintos valores de alineación de elementos afectan contenedores secundarios. De arriba a abajo: flex-start
, center
, flex-end
y stretch
.
Nota
También hay una propiedad llamada align-self
, que permite definir el comportamiento align-item
para cada hijo flex particular. Estos se imponen sobre align-items
.
justify-content
sobre el eje principal
La otra propiedad principal de esta categoría que se utiliza mucho es justify-content
, que especifica cómo se disponen los elementos sobre el eje principal, en términos de lo que sucede con el espacio blanco sobrante entre los hijos. Esta propiedad no tiene ningún efecto cuando se ha establecido que hijos y márgenes ocupen todo el espacio disponible sobre el eje principal, como es el caso de mi ejemplo principal. Por lo tanto, he creado otro ejemplo para demostrar el uso de justify-content
: por favor, echa un vistazo al ejemplo Flexbox de ancho fijo.
En este ejemplo he hecho que los hijos FlexBox ocupen un porcentaje dado de la anchura del eje principal:
#first {
width: 25%;
}
#second {
width: 40%;
}
#third {
width: 25%;
}
Luego, en el contenedor padre he dado un valor de justify-content
de la siguiente manera:
footer {
display: flex;
flex-flow: row wrap;
align-items: stretch;
justify-content: space-around;
}
Este valor funciona bastante bien: repartir todo el espacio entre los elementos hijos y en el exterior de todos ellos en cantidades iguales. Los otros valores disponibles son los siguientes:
flex-start
: Agrupa todos los hijos FlexBox al principio del eje principal, con todo el espacio al final.flex-end
: Agrupa todos los hijos FlexBox al final del eje principal, con el espacio todo al principio.center
: Agrupa todos los hijos Flexbox en el centro del eje principal, con el espacio repartido por igual en cada extremo.space-between
: tiene un efecto muy similar aspace-around
, pero sin espacio asignado a cada extremo de la serie de hijos Flexbox.
La Figura 4 muestra el efecto de los diferentes ajustes de justify-content
:
Figura 4: Muestra de cómo los distintos valores de justify-content
afectan a los contenedores hijos. De arriba a abajo - flex-start
, center
, flex-end
y space-around
.
align-content
para alinear Flexboxes multilínea
También se puede especificar cómo se distribuirá el espacio sobrante entre múltiples líneas de hijos Flexbox, en el caso Flexboxes de varias líneas, es decir, cuando se ha usado la palabra wrap
. De hecho, sólo tiene efecto cuando los hijos Flexbox ocupan una cantidad fija de espacio en la dirección del eje transversal, o una altura fija en el caso de filas. Los valores disponibles son:
flex-start
: Agrupa todos los hijos Flexbox al principio del eje transversal, con todo el espacio al final.flex-end
: Agrupa todos los hijos Flexbox al final del eje transversal, con todo el espacio al principio.center
: Agrupa todos los hijos Flexbox en el centro del eje transversal, con el espacio dividido por igual en cada extremo.space-between
: Comparte todo el espacio entre los elementos hijos y fuera de ellos sobre todo el eje transversal en cantidades iguales.space-around
: tiene un efecto muy similar aspace-between
, pero con menos espacio dado a cada extremo de la serie de hijos Flexbox.
Alterar el orden de disposición de elementos
Tradicionalmente ha sido doloroso cambiar el orden en que los elementos se visualizan sin jugar un poco con el orden del código fuente. No con Flexbox. Flexbox permite establecer la propiedad order
a los elementos hijos para indicar el orden en que aparecerán en la fila o columna Flexbox. Esta propiedad toma un valor entero — llamado grupo ordinal— y cuanto mayor sea el grupo ordinal, más tarde aparecerá el hijo. Así, por ejemplo, volviendo a mi ejemplo de fat footer flexible, la caja de enlaces es el segundo elemento hijo por defecto, tal y como se muestra en la figura 5.
Figura 5: El orden predeterminado de los hijos del pie de página: contacto, enlaces, derechos de autor.
Por defecto, todos los hijos Flexbox están en el grupo ordinal 0. Podemos cambiar fácilmente este orden dando a los hijos diferentes valores de grupo ordinal. Los valores más altos aparecerán antes en la lista de hijos flex: el orden de los hijos con un mismo grupo ordinal siempre se regirá por el orden en el código fuente. Así que en mi ejemplo, he hecho que los enlaces aparezcan al final estableciendo su grupo ordinal a 1 (véase el resultado en la Figura 6):
#second {
order: 1;
}
Figura 6: La propiedad order
nos ha dado un nuevo orden para los hijos del pie de página.
Nota
Pueden usarse valores negativos de order
.
Dar flex a los elementos
Probablemente la característica más poderosa de Flexbox es la capacidad de ajustar la longitud de los elementos hijos en la dirección del flujo de flex (width
si flex-flow
es row
o height
si flex-flow
es column
) a una cantidad flexible dependiendo de la cantidad de espacio disponible en el elemento padre en esa dirección. Esto se hace utilizando la propiedad flex
, cuyo valor puede tomar un total de tres partes. Añadámoslas una a una y veamos el efecto que tienen. En primer lugar, un "factor de crecimiento flex":
#first {
flex: 1;
}
#second {
flex: 1;
}
#third {
flex: 1;
}
Son valores sin unidades que sirven como proporción: dictan qué cantidad de espacio disponible en el interior del padres debe tomar cada hijo. Si todos se ponen a 1 a cada hijo se le dará un tamaño igual dentro de la Flexbox. Si se diese a uno de los hijos un valor de 2, ese tendría el doble de espacio que los demás:
#first {
flex: 1;
}
#second {
flex: 2;
}
#third {
flex: 1;
}
También se puede configurar un ancho preferido para cada hijo, así:
#first {
flex: 1 200px;
}
#second {
flex: 2 300px;
}
#third {
flex: 1 250px;
}
En primer lugar, las anchuras preferidas se aplican a cada hijo. Después el espacio restante en el interior del elemento padre se divide de acuerdo a los factores de crecimiento flex y se reparte a los hijos para llegar a su anchura final. Así, a los hijos se les darían tamaños a lo largo del eje principal de 200, 300 y 250 píxeles, para un total de 750 píxeles. Si el contenedor primario fuese de 950 píxeles a lo largo del eje principal, entonces habría un espacio adicional de 200 píxeles para distribuir entre los hijos. A los hijos primero y tercero se les daría 50 píxeles, ya que tienen un factor de crecimiento flex de 1. Su tamaño final sería de 250 píxeles y 300 píxeles respectivamente. Al segundo hijo se le darían 100 píxeles, ya que tiene un factor de crecimiento flex de 2. Su tamaño final sería de 400 píxeles.
La tercera parte del valor flex se utiliza muy poco, pero vamos a verla si acaso. También se puede dar a los elementos hijos un "factor de contracción flex", así:
#first {
flex: 1 1 400px;
}
#second {
flex: 2 3 600px;
}
#third {
flex: 1 2 400px;
}
Los factores de contracción flex, a pesar de su nombre, son valores positivos: los segundos valores sin unidades en las declaraciones anteriores. Estos sólo entran en juego cuando los hijos desbordan su contenedor padre en la dirección del eje principal. También actúan como valores de proporción, pero esta vez especifican la proporción de la "cantidad de desbordamiento" (la cantidad en que los hijos desbordan el contenedor) que se deducirá del tamaño de cada hijo, para que el tamaño total se iguale al tamaño del padre: para detener el desbordamiento, en la práctica.
Pongamos que el contenedor primario es de 1100 píxeles a lo largo del eje principal. Así, los hijos lo desbordarían por 300 píxeles (equivalen a 1400 píxeles a lo largo del eje principal, en total). Debido a los factores de contracción flex que les hemos fijado:
- Al primer hijo se le quitaría 1/6 de la cantidad de desbordamiento, 50 píxeles. Su valor calculado, por tanto, será de 350 píxeles.
- Al segundo hijo se le quitaría 3/6 de la cantidad de desbordamiento, 150 píxeles. Su valor calculado, por tanto, será de 450 píxeles.
- Al tercer hijo se le quitaría 2/6 de la cantidad de desbordamiento, 100 píxeles. Su valor calculado, por lo tanto, sería de 300 pixeles.
Así que un mayor factor de encogimiento flex en realidad se traduce en un elemento más pequeño.
Los valores con que acabé en mi ejemplo principal son los siguientes:
#first {
flex: 1 0 7rem;
}
#second {
order: 1;
flex: 2 0 8rem;
}
#third {
flex: 1.5 0 7rem;
}
Flexbox: ventajas responsive
Una cosa que ha funcionado bastante bien en mi ejemplo es combinaR un Flexbox multilínea (flex-flow: row wrap
) con una longitud flex preferida para los elementos hijos (por ejemplo, flex: 1 0 7rem ;
), y las media queries. En ventana estrechas, la anchura preferida no puede alcanzarse sin desbordamiento del elemento padre y, por lo tanto, los hijos Flexbox se disponen en líneas múltiples para mantener las cosas con buen aspecto, como se muestra en la Figura 7.
Figura 7: Una aplicación sencilla de Flexbox nos da un diseño responsive útil.
flex: auto
y flex: initial
Flex tiene algunos otros valores útiles, descritos en la parte de la especificación titulada Valores comunes de 'flex'. Dos de los más útiles son auto
e initial
. Dar flex: auto;
a un elemento hijo de una caja flexible (equivalente a flex: 1 1 auto
) hará que sea totalmente flexible y le dará tamaño de acuerdo a cualesquiera propiedades width/height
o min/max-width/height
que se hayan fijado; se expandirá para ocupar una proporción de cualquier espacio libre disponible, pero luego se encogerá para ajustarse a su contenido cuando no haya espacio libre extra. Esto puede tener algunos efectos interesantes cuando se combina con un min-width
, por ejemplo. Echa un vistazo a mi ejemplo de Flex auto. En este ejemplo, al contenedor padre se le da flex-flow: row
y al tercer contenedor hijo se le da flex: auto;
ala vez que tiene un min-width
. Por lo tanto, se expande hasta llenar cualquier espacio sobrante en la línea, sin importar si la barra de herramientas es de varias líneas o no, y entonces se encoge cuidadosamente a medida que se hace más pequeño, lo que permite que los botones hijos se reorganizan para adaptarse.
Intenta cambiar el valor de flex: auto;
por flex: initial
(equivalente a flex: 0 1 auto
) y verás que el tercer contenedor hijo deja de aumentar de tamaño cuando hay exceso de espacio, pero aún así se encoge si es necesario.
Toda esta potencia y flexibilidad con tan poco código es definitivamente una buena cosa.
Degradación grácil
Flexbox es un modelo completamente nuevo de diseño, y los navegadores que no lo entienden simplemente lo ignorarán. Esto podría parecer un ultimátum que nos impide usarlo. Sin embargo, no es necesariamente así. Por ejemplo, podrían usarse floats o tablas CSS para el diseño "desktop" del sitio, y optar por utilizar Flexbox para anchos de navegador muy pequeños (móvil), tal vez para despalazar una navegación del lado izquierdo a la parte inferior de la página principal.
Si se da a todos los elementos de la página (navegación, encabezado, pie de página, etc) display: block;
por defecto antes de las reglas FlexBox, un navegador con soporte para media queries, pero no para FlexBox simplemente linealizará el contenido en bloques que abarcan todo el ancho del dispositivo. Se quedarían en el orden del código fuente, pero todo el contenido será accesible para el usuario.
Como alternativa, las tablas CSS pueden utilizarse para eliminat un elemento del código fuente y ponerlo al principio o al final del contenido: mira el sucio hack table-caption y ríe entre dientes mientras das unos pasos atrás.
Resumen
Espero que este artículo te haya dejado claro exactamente lo impresionante y útil que es Flexbox, que nos permite acabar con los continuados abusos de float y clear, y toda una serie de otras técnicas de siseño horribles, "hacky" e inflexibles. También hemos visto lo genial que es Flexbox para el diseño web responsive cuando se combina con otras tecnologías, como las media queries.
Nota
Imagen de portada de Horia Varlan.