sábado, 29 de marzo de 2014

CSS Float: Tutorial Visual (I)

Float es una propiedad CSS que define un tipo de posicionamiento de los elementos de una página web. En el estándar se definen tres esquemas de posicionamiento para presentar elementos:

  1. Normal.  (y aquí se incluye también el posicionamiento relativo).
  2. Absoluto.
  3. Flotante.

El flotante es, quizá, el menos intuitivo de los tres y el que presenta particularidades más ‘extrañas’ que intentaremos aclarar completamente en esta serie de dos totorales.

Conceptos Básicos

Para que un elemento flote utilizamos la propiedad float que puede tener los siguientes valores:

  • left – Flota a la izquierda
  • right – Flota a la derecha
  • none – No flota, valor por defecto
  • inherit – Hereda el valor del padre. No se utiliza porque en IE no funciona

Como vemos, un elemento no puede ser simplemente flotante, tiene que ser flotante a la derecha o flotante a la izquierda:

#elemento1 {
    float: right;
}
#elemento2 {
    float: left;
}

Que un elemento flote significa que intentará siempre desplazarse a la izquierda o a la derecha sobre la línea actual hasta chocar con el borde de su caja contenedora o de otro elemento flotante. Sólo otro elemento float puede interponerse entre él y el borde del contenedor. Veremos muchos ejemplos en los próximos apartados para entender todas las implicaciones de este comportamiento.

Hay cuatro reglas importantes que debemos tener en cuenta. Conociéndolas podremos siempre predecir dónde quedará un elemento si lo flotamos:

Reglas de los elementos float

  1. Los elementos por encima del float no se ven afectados, no cambian su posición.
  2. El elemento float se desplaza a derecha o izquierda hasta el borde de su contenedor o hasta chocar con otro elemento flotante.
  3. Los elementos de bloque se comportan como si el float no estuviera en la página, ocupando su hueco.
  4. Los elementos de línea, cuando quedan al lado de un float acortarán su longitud si es posible, para dejarle espacio.

Veremos también que este comportamiento se puede modificar con propiedades como clear o overflow.

Flotando elementos de bloque

Vamos a empezar viendo cómo se comportan los elementos de bloque (divs en este ejemplo) cuando los flotamos. Trabajaremos con un documento con tres divs, cada uno con un fondo de color diferente.

En el HTML simplemente hemos declarado el título y tres divs vacíos dentro de un div (#wrapper) que los envuelve a todos:

 <div id="wrapper">
  <h1>Sólo Bloques (Divs)</h1>
  <div id="verde"></div>
  <div id="naranja"></div>
  <div id="azul"></div>
 </div>

Por CSS les hemos puesto un borde punteado, altura, anchura y un color de fondo diferente a cada uno para que los ejemplos queden más claros

La página se verá así en pantalla:

Si asignamos float:right al bloque naranja se desplazará a la derecha y saldrá del flujo de elementos de la página para los otros elementos de bloque, que ocuparan su lugar como si no existiera:

¿Y si lo flotamos hacia la izquierda?

¿Qué ha pasado? Lo mismo que antes, el bloque flotante (naranja) sale del flujo de elementos de la página, los bloques que están debajo ocupan su lugar como si no existiera (el azul sube). El naranja se coloca en su línea hacia la izquierda hasta el borde del contenedor tapando el bloque azul.

Para los elementos de bloque, los float están fuera del flujo de elementos de la página, no ocupan espacio

¿Y si flotamos también el bloque azul?¿qué ocurrirá?

Como siempre, los bloques que están por encima no se ven afectados, el título (h1) y el bloque verde siguen en su sitio, pero por debajo nos llama la atención que el bloque contenedor (#wrapper) se ha encogido dejando fuera los dos bloques con float:left. La razón es sencilla, para los elementos de bloque (como el div contenedor, con borde negro en la imagen), los elementos flotantes no ocupan espacio en la página, por lo tanto no los tiene en cuenta. Veremos esto en detalle mas adelante y veremos también varias formas de evitarlo.

Para saber en que orden quedan los bloques es importante saber cómo están declarados en el código HTML. En este caso el naranja está primero, flota hacia la izquierda y sále del flujo de elementos, por lo que el azul sube a su misma línea, después éste también flota hacia la izquierda por lo que se intentará colocar al lado del borde o de otros elementos flotantes anteriores. En este caso le toca detrás del naranja.

Si flotamos también el verde el resultado es:

¿Y si flotamos también el título, al fin y al cabo es otro elemento de bloque (h1)?

Como el título es el primer elemento, flota en su línea a la izquierda, los demás elementos van subiendo y flotando en su misma línea detrás de él. Cuando no hay sitio en la misma línea se desplazan a la siguiente, donde hay hueco para seguir colocándose. Y mientras tanto, el div contenedor a lo suyo, encogiendo hasta casi desaparecer.

Cuando un elemento fotado cambia de línea por falta de espacio, es muy importante tener en cuenta la altura de las cajas de cada uno, porque el elemento se quedará donde tenga un hueco y puede quedar colocado de forma extraña:

En este caso el bloque azul no cabe al lado del naranja y se desplaza a la línea siguiente pero se ha quedado ‘enganchado’ en el verde porque es unos pixels más alto que el naranja. Al cambiar de línea quedará en el primer lugar que quepa, tocando al bloque de arriba. En este caso el problema se ve muy claro, pero cuando no tenemos fondos de color y un borde marcado, los elementos quedan descuadrados y el problema no se ve fácilmente.

La siguiente situación es parecida pero ahora el bloque azul es más ancho que la distancia que queda entre el verde y el borde del contenedor, como no cabe debajo del naranja, se irá hasta el extremo:


Elementos de Línea: Imágenes y Texto

Las líneas de texto, y cualquier elemento de línea en general (span, em, strong, etc), adaptarán su longitud al colocarse al lado de un elemento flotante, para dejarle hueco.

Vamos a empezar viendo como se comportan las imágenes, que actúan como elementos de línea aunque en realidad son una mezcla, inline-block, porque fluyen en la línea pero tienen altura y anchura definible con width y height.

Veamos un documento con tres imágenes:

¿Qué ocurrirá si flotamos el pez azul hacia la izquierda?

img#pezAzul {
    float:left;
}

El pez azul se desplazará alegremente en su línea hacia la izquierda hasta el borde del contenedor (o hasta chocar con otro elemento flotante, pero en este caso no hay ninguno) y los otros elementos de línea (las otras dos imágenes), al estar al lado de un float, le harán hueco:

Si lo flotamos a la derecha:

La imagen se desplaza a la derecha hasta el borde y los otros elementos le dejan hueco.

¿Y si las imágenes estuvieran dentro de bloques, en un div? Para saber cómo van a quedar los elementos es importante saber en que orden están declarados en el código. Declaramos primero un título, luego la imagen del pez, y luego un div con borde rojo que contiene dos imágenes, la mariquita y el smiley:

 <h1>Imágenes en Divs</h1>
 <img src="images/fish1.png">
 <div class="bordeRojo">
  <img src="images/bug.png">
  <img src="images/smile.png">
 </div>

Las dos imágenes de abajo están contenidas en un div, que es un elemento de bloque y ocupa su propia línea.

Si ahora flotamos la imagen del pez hacia la izquierda:

Lo que ocurre es lo siguiente:

  1. La imagen sále del flujo normal de elementos de la página por lo que no ocupa espacio (para los elementos de bloque)
  2. El bloque de abajo (con borde rojo) sube para ocupar su espacio
  3. Los elementos de línea (las 2 imágenes) se encuentran ahora al lado de un float y se desplazan para hacerle hueco

Este comportamiento se ve mucho más claro con las líneas de texto, que también son elementos de línea (faltaría mas) y normalmente están contenidas dentro de un párrafo (<p>…</p>) que es un elemento de bloque.

En el siguiente ejemplo tenemos un documento con un título, una imagen y un párrafo:

Si flotamos la imagen a la izquierda el bloque de texto subirá como si la imagen no existiera. Las líneas quedan ahora al lado de un float y se acortarán para fluir por el lado de la imagen:

Si la flotamos a la derecha:


Terminando por hoy …

Aqui vamos a terminar la primera entrega de este tutorial, en la segunda parte veremos:

  • El uso de la propiedad clear, con numerosos ejemplos para ver cómo afecta a elementos de bloque y de línea, dependiendo de si son flotantes o no y cómo podemos utilizarlo para controlar la disposición de los elementos.
  • Porqué se produce el ‘problema’ del contenedor, que se encoge hasta desaparecer cuando contiene elementos flotantes. Veremos que esto no es un error, sino una decisión de diseño. Sabremos el porqué y varias formas diferentes de evitarlo.
  • Cómo utilizar los floats para diseñar la estructura de una página.

viernes, 7 de marzo de 2014

Atributos 'defer' y 'async' en <script>

Cuando incluimos un script en una página mediante las etiquetas <script></script>, éste se cargará de forma síncrona bloqueando el resto de la página hasta que se haya ejecutado. Ocurre lo mismo tanto si el código es inline como si es un archivo remoto. En la entrada "Los scripts externos pueden bloquear tu web" explicaba en detalle las diferencias entre carga síncrona (bloqueante) y carga asíncrona (no bloqueante).

Aunque la presentación está bloqueada, los navegadores modernos buscan otros ficheros externos que tengan que cargar y lanzan la petición (js, imágenes, css). La descarga sí se inicia, pero toda la presentación de elementos está bloqueada hasta que se ejecuten. Por esto se recomienda colocar los scripts al final, para que todo el HTML se haya presentado ya en pantalla y el usuario tenga una sensación de carga rápida.

Hay dos atributos que podemos incluir en la etiqueta <script> para modificar este comportamiento y forzar la carga asíncrona (no bloqueante):

  • defer <script defer src="myFile.js"></script>
  • async (HTML5) <script async src="myFile.js"></script>

Los dos son atributos booleanos y tienen un funcionamiento similar. La diferencia principal es el momento en que se ejecuta el código descargado. En el caso de async se ejecuta en cuanto la descarga se ha completado, mientras que en el caso de defer se ejecuta cuando se ha terminado de parsear toda la página.

Atributo 'defer'

Microsoft introdujo defer en el Internet Explorer 4 para evitar que los scripts bloquearan la presentación del resto de la página si no era necesario (si no incluian elementos nuevos).

Se utiliza de la siguiente manera:

    
<script defer src="myFile.js"></script>

Se estandarizó en HTML4 con el siguiente texto:

"defer
Este atributo booleano proporciona una indicación para el UA [browser] de que el script no va a generar ningún contenido en el documento (p.ej. el javascript no contiene document.write) y, por lo tanto, el UA puede continuar con el parseo y presentación"

En HTML5 se especifica de la siguiente forma:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...]si el atributo defer está presente [sin el atributo sync] el script se ejecuta cuando la página se ha terminado de parsear.

Es decir, este atributo se utiliza únicamente para scripts externos, se ignora para scripts inline. Algunas versiones de IE (IE<10) no respetan esta indicación del estandar y pueden aplicar defer a código en línea.

Los scripts se ejecutan justo antes de lanzarse el evento DOMContentLoaded.

Además, en otro punto del estandar se indica que los scripts con defer deben ejecutarse en el orden en que se especificaron en la página.

Atributo 'async'

async se introduce en HTML5 para indicar que el script debe cargarse de forma asíncrona, sin bloquear la presentación del resto de contenido, y ejecutarse inmediatamente cuando se haya descargado. El orden de ejecución no se garantiza.

    
<script async src="myFile.js"></script>

La especificación de HTML5 indica lo siguiente:

async y defer son atributos booleanos que indican cómo se deben ejecutar los scripts. Los atributos defer y async no deben especificarse si no existe el atributo src.

[...] Si el atributo async está presente el script se ejecuta de forma asincrona tan pronto como esté disponible.

En este caso el evento DOMContentLoaded también esperará a que el script se ejecute.

Los dos juntos: 'async' + 'defer'

Cuando los dos atributos aparecen juntos, async prevalece sobre defer si el navegador lo soporta. defer puede añadirse como fallback para navegadores antiguos:

El atributo defer puede especificarse incluso si el atributo async está presente, para que los navegadores antiguos que sólo soportan defer utilicen esa opción en vez de la descarga síncrona bloqueante que sería la opción por defecto.

Debido a las diferentes implementaciones es mejor evitar dependencias y no asumir, en ningún caso, que los scripts se ejecutarán en orden ( con defer). Evitaremos problemas si consideramos siempre que se descargarán de forma asíncrona pero no se garantiza el orden de ejecución.

Inyectando un <script> dinámicamente

Otra opción muy usada para cargar un script de forma asíncrona es inyectar la etiqueta <script> dinámicamente, con javascript:

    

var script = document.createElement("script");
script.src = "/path/to/script.js";
document.getElementsByTagName("head")[0].appendChild(script);

Los <script> que se añaden dinámicamente al documento son asíncronos por defecto, no bloquean la presentación de la página y se ejecutan en cuanto se han descargado. El mismo comportamiento que con el atributo async.

En este caso el evento DOMContentLoaded no espera a que el script se haya ejecutado.

A veces podemos encontrar también que se incluye el atributo *async* en este tipo de scripts:

var script = document.createElement("script");
script.src = "/path/to/script.js";    
script.async = true;               //set async attribute 
document.getElementsByTagName("head")[0].appendChild(script);

Es únicamente para los navegadores Firefox 3.6 y Opera, que no tienen el comportamiento asíncrono por defecto para *scripts* inyectados pero soportarn el atributo *async*.

Fuentes:

W3C HTML5 Scripting
HTML: The Markup Language (an HTML language reference)
Deep dive into the murky waters of script loading