domingo, 30 de septiembre de 2012

Preservando el contexto *this*. $.proxy() y bind()

Como hemos visto en algún post anterior, el valor de this en una función representa el contexto en el que se ejecuta y depende de cómo se la llame. Cuando utilizamos la función como un callback, es muy probable que el valor de this no sea el que esperamos cuando finalmente se ejecuta.
Supongamos que tenemos el siguiente objeto:
var myObject = {
    myName: "Hulk",
    showMyName: function() {  alert(  "My Name Is: " + this.myName );}
}
Es muy importante que, cuando utilicemos showMyName() como callback, this siga haciendo referencia al objeto myObject, para que this.myName sea “Hulk”. Sin embargo, la mayoría de las veces no será así. Al ejecutarse desde otro contexto, el valor de this será diferente. Por ejemplo:

//como callback en setTimeout
setTimeout( myObject.showMyName, 1000); 

//como callback en el click de un botón
$(".button1").click( myObject.showMyName );


En los dos casos perdemos el contexto original y el resultado es:

“My Name Is: undefined”

En el primer caso this será el objeto global window y en el segundo caso será el elemento button del DOM. Ninguno de estos dos objetos tiene una propiedad llamada myName, por lo que this.myName es undefined en ese contexto.

A veces necesitamos asegurarnos de que un callback se va a ejecutar con el contexto que nosotros necesitamos. Es decir, que el valor de this no nos va a dar sorpresas y tendrá el valor que queramos. En cualquier proyecto grande necesitaremos una función que nos asegure esto.

Muchas librerias y frameworks proporcionan una solución propia a este problema, por ejemplo jQuery tiene la función $.proxy(), Prototype tiene una función bind(), Ext JS tiene createDelegate(), Dojo tiene hitch(), etc. La última versión de JavaScript, ECMAscript 5, ha incluido también el método .bind() como solución nativa.

Si tenemos que dar soporte a navegadores que no soportan bind() de ECMAscript 5 y no tenemos necesidad de incluir ninguna de estas librerías o frameworks, podemos crear nosotros mismos nuestra utilidad para esto.

Creando nuestra función proxy

Lo que tenemos que hacer es sencillo, envolver la función original en otra que nos asegure el contexto y que se pueda utilizar como callback en lugar del método original. Es decir, una función intermediaria ( proxy ).

Vamos a crear una función createProxyFunction( context, originalFunc ) con dos argumentos: el contexto que debemos preservar ( this ) y la función original. Devolverá una función que puede usarse exactamente igual que la original, pero asegurando el valor de this. El uso sería tan sencillo como:

var myProxyFunc = createProxyFunction( myObject,  myObject.showMyName ); 
$(".button1").click( myProxyFunc );


o, ahorrandonos la variable intermedia:

$(".button1").click( createProxyFunction( myObject,  myObject.showMyName) );


Podemos hacer esto simplemente utilizando los métodos call() o apply() que tienen todas las funciones en JavaScript. Devolveremos una función que llamará a la original utilizando apply() para asignar el contexto.

Una implementación simple sería:

function createProxyFunction ( context, originalFunc ) {
    var proxyFunction = function() {
        return originalFunc.apply( context, arguments );
};
return proxyFunction;
} 


Podríamos complicarlo un poco para permitir añadir argumentos ‘locales’ que se sumarían a los argumentos con los que se llamará al callback cuando se ejecute:

function createProxyFunction ( context, originalFunc ) {
    //store additional arguments (if any) appart from context and originalFunc 
    var proxyArgs = Array.prototype.slice.call(arguments, 2);
    var proxyFunction = function() {
        var allArgs = proxyArgs.concat ( Array.prototype.slice.call(arguments) );
        return originalFunc.apply( context, allArgs );
};
return proxyFunction;
} 


Utilizamos la función slice() de la ‘clase’ Array porque arguments es un pseudo-array y no la tiene. El valor que devuelve sí es un array real y por eso podemos utilizar concat().

La función que se devuelve en este caso tiene concatenados los argumentos que pasemos a createProxyFunction() (despues de context y originalFunc ) con los que se incluyan en la función original por parte de quien ejecute el callback. Es lo mismo que hace $.proxy() de jQuery.

¿Para qué sirven los argumentos extra?

La opción de añadir parámetros extra es muy útil porque podemos estar manejando información, externa al objeto que enviamos como contexto, que necesitaremos conocer cuando recibamos la llamada a nuestro callback.

Los parámetros que recibimos normalmente en un callback estarán fijados por un API. Por ejemplo, para un evento click, jQuery lanzará nuestro callback con eventObject como único argumento:

eventHandler( eventObject)

Si utilizamos la función $.get() de jQuery para pedir unos datos por AJAX, nuestro callback será invocado cuando los datos estén listos con los argumentos:

myAjaxCallback( data, textStatus, jqXHR )

Puede ocurrir que estemos manejando unos datos concretos y, en función de estos, hagamos una llamada AJAX. La respuesta de la llamada AJAX nos llegará cuando se invoque nuestro callback, pero necesitamos seguir teniendo acceso a los datos que estábamos manejando antes para terminar la tarea. La solución sería añadir estos datos como parámetros extra:

$.get('ajax/test.html', createProxyFunction( myObject, myAjaxCallback, extraParam1, extraParam2 );

Por supuesto la función myAjaxCallback tiene que estar preparada para recibir todos estos parámetros:

myAjaxCallback( extraParam1, extraParam2, data, textStatus, jqXHR ); 


ECMAScript 5 bind()

En la implementación de ECMAScript 5, bind() es un método nativo de las funciones que devuelve una nueva función con el contexto fijado. En nuestro ejemplo lo utilizaríamos así:

$(".button1").click( myObject.showMyName.bind( myObject ) );


También pueden añadirse parámetros extra si los necesitamos:

$(".button1").click( myObject.showMyName.bind( myObject, extraParam1, extraParam2 ) );


Recibiremos los parámetros extra delante:

callbackFunction ( extraParam1, extraParam2, param1, param2 );


En que se diferencia de myObject.showMyName.call( myObject )?

A veces se plantea la pregunta de si podríamos usar directamente:

$(".button1").click( myObject.showMyName.call( myObject ) );


en vez de :

$(".button1").click( myObject.showMyName.bind( myObject ) );


No es lo mismo. La diferencia es que bind() devuelve una función que es la que actúa como callback. Utilizando call() estamos ejecutando directamente el método, no sirve como callback. Las dos expresiones no son equivalentes.


viernes, 28 de septiembre de 2012

Ajustar el ancho de un DIV al contenido

Por defecto el ancho de un div (o de cualquier elemento de bloque) se expande hasta ocupar el 100% del espacio disponible. A veces podemos necesitar cambiar este comportamiento para que se ajuste automáticamente al ancho de su contenido:


Hay varias formas de conseguir esto pero todas son ‘pequeñas chapuzas’ y tienen efectos secundarios. CSS no proporciona una forma elegante de hacerlo.
En el estándar CSS, ajustar el ancho al contenido es sólo un recurso para las situaciones en las que se necesita asignar un ancho y no hay uno definido ni fijado por defecto. Lo que necesitamos es crear una de estas situaciones, que son:
  • float
  • posición absoluta
  • inline-block ( o inline)
  • table

Float

.myDiv {
    float:left;  /* o float:right;*/
}
Haciendo el elemento flotante conseguimos el objetivo, pero es necesario conocer muy bien las propiedades de los elementos flotantes porque los efectos secundarios afectan al comportamiento del div y al resto de contenido.

Posición absoluta

.myDiv {
    position:absolute;
}
Esta solución también tiene efectos secundarios importantes:

  • El div sale del flujo natural de elementos en la página y no ocupa espacio para el resto de elementos, que pueden descolocarse.
  • Se posiciona con respecto al elemento que lo contiene ( si éste está posicionado ).


Inline-block

.myDiv {
    display:inline-block;
}
Esta solución es, quizá, la más elegante. Hay un par de cosas que tenemos que tener en cuenta:
  • El elemento se colocará ‘en línea’ con otros elementos, si los hay.
  • Para que funcione en IE7 necesita un hack o cambiar el div por un span.
El problema con IE7 ( y con IE6 pero ya es hora de ir descartándolo ) es que sólo soportan inline-block para elementos que son inline por defecto. Por eso, a parte del hack, una solución sencilla es cambiar el div por un span y aplicarle display:inline-block. El resultado en el resto de navegadores será el correcto.

Table

.myDiv {
    display:table;
}
Dejando aparte el estigma que tiene la palabra table, este es un método que apenas tiene efectos secundarios. El único problema es que no funciona en IE6/7.

Ajustar al contenido es costoso (poco eficiente)

Siempre que podamos es preferible utilizar anchos fijos o anchos asignados por defecto. Para el navegador es un proceso mucho más sencillo. Para presentar un elemento de bloque que se ajuste automáticamente a su contenido debe primero calcular los tamaños de todos los contenidos y despues volver al elemento contenedor para calcular su anchura y presentarlo.


Fuentes:
CSS “shrink wrap”
StackOverflow Cross browser inline-block

domingo, 23 de septiembre de 2012

JavaScript: El contexto *this* en callbacks

El valor del puntero this en JavaScript es uno de los grandes enigmas con que los que se enfrenta el que empieza a manejar el lenguaje. En principio parece sencillo. Dentro de un objeto, el puntero this hace referencia al propio objeto ¿o no?:
var myObject = {
    message: “Hello!”,
    talk: function() {
        alert(“I say: ” + this.message);
    }
}
cuando hacemos referencia a this.message estamos apuntando a la propiedad message en el contexto del objeto myObject. Por lo tanto this es una referencia al objeto en el que estamos.
Si hacemos:

myObject.talk();

veremos el mensaje:

"I say: Hello"

Parece fácil, pero se complica y mucho.

El contexto de un callback

Supongamos que queremos utilizar el método myObject.talk() como callback o event handler que se ejecuta al pulsar un botón:
$(".button1").click( myObject.talk );
Al hacer click en el botón veremos:

"I say: undefined"

¡¿Undefined?!. Cuando se llama al método myObject.talk al hacer click en el botón, el valor de this.message es undefined. La razón es que this ya no apunta al objeto myObject, hace referencia a otro contexto diferente.

En este caso, al ser un callback de un evento del DOM, el navegador asocia this al elemento que provoca el evento ( si usamos addEventListener,  que es la opción que utiliza jQuery si el navegador lo permite ). Es decir, this es el objeto button. Como dentro de este objeto no hay ninguna propiedad que se llame message, this.message es undefined.

No es el único caso en que se cambia el contexto. Si lo usamos como callback de una llamada AJAX o de setTimeout() tampoco tendrá el valor original.

Y por si las cosas no fueran ya bastante complicadas, resulta que si ponemos el método dentro de una función anónima, el mensaje es correcto:

$(".button1").click(function() {
    myObject.talk();
});


El resultado es:

"I say: Hello!"

Es casi igual que lo que teníamos antes, llamámos al método myObject.talk() pero ahora dentro de una función ¿por qué ahora sí funciona?. Bueno, el valor de this ahora sí es el objeto myObject. Lo explicaremos un poco más adelante.

Vamos a ver como se comporta el puntero this en funciones/métodos y después volveremos sobre este ejemplo para explicar qué es lo que está pasando.

this en funciones y métodos

Cuando tenemos una referencia a this dentro de una función, su valor dependerá siempre de cómo se llama a la función. 

Por ejemplo, si hacemos:
var f1 = myObject.talk;
f1();  //this.message is undefined
Estamos llamando a la función talk() de una forma diferente, desde fuera del objeto. En este caso this será el contexto desde el que ejecutamos f1(), que es el objeto global (window).

 Sin embargo, si ejecutamos la función como un método de un objeto concreto, el valor de this será ese objeto:

myObject.talk(); //this.message is "Hello!"

Podríamos incluso llamar a la misma función desde otro objeto diferente:

var Object2 = {
  message: "I'm OBJECT2",
}

Object2.talk = myObject.talk;

Object2.talk(); //this.message is "I'm OBJECT2"

En el alert en pantalla veremos:

"I say: I'm OBJECT2"

Volviendo al ejemplo del callback

Ahora podemos entender qué estaba pasando en nuestros dos ejemplos iniciales. Recordemos que teníamos la función talk() como callback de dos formas diferentes:
//Example 1
$(".button1").click( myObject.talk );

//Example 2
$(".button1").click(function() {
    myObject.talk();
});


En el primer caso le estamos pasando una referencia directa a nuestra función, para que la ejecute cuando y como quiera. Como hemos visto antes, el valor de this dependerá de cómo se llame a la función. Nosotros ya no tenemos el control sobre cómo se la llamará, por eso el valor del contexto ya no es nuestro objeto.

El navegador, cuando llame al callback, colocará el contexto del objeto del DOM que ha lanzado el evento.

En el segundo caso se llama a la función anónima también con el contexto del elemento button, pero dentro de esta función estamos ejecutando explícitamente un método del objeto myObject:


myObject.talk();

Es una llamada directa al método dentro del objeto concreto y, como hemos visto antes, el contexto será el del objeto que se referencia al llamarlo. Por eso aquí el puntero this es correcto.

Hay varias formas de asegurarnos de que el contexto con el que se llama a una función es el que nosotros queremos, como Function.apply(), Function.call() o utilizando una función proxy, pero esto lo veremos en otro post.

 Fuentes:
  MDN This


viernes, 21 de septiembre de 2012

¿Que es el HTML5 Shiv?

HTML5 Shiv (o Shim) es un hack necesario para poder utilizar los nuevos elementos semánticos de HTML5 (header, footer, article, etc ) en IE8 y anteriores. Vamos a ver en detalle cual es el problema y cómo se soluciona.

Internet Explorer ignora las nuevas etiquetas

El problema es que al utilizar las nuevas etiquetas para estructurar una página web, la presentación final va a depender totalmente de los estilos que apliquemos a esas etiquetas. El Internet Explorer ( versión 8 y anteriores ) no aplica los estilos a las etiquetas nuevas porque no las reconoce. Ignora las etiquetas y los estilos asociados.

Si hemos creado un documento utilizando, por ejemplo:

My Great Web Header

y los correspondientes estilos en CSS:

header {
    border: 1px solid black;
    background-color: wheat;
    font-size: 160%;
    padding: 10px;
}

Lo que esperamos ver es algo así:

Lo que verán los visitantes que utilicen el IE8 o inferior es:

Esto evidentemente nos obliga a buscar alguna solución si tenemos que dar soporte a estos navegadores y queremos utilizar las etiquetas nuevas. Esta solución es lo que se ha llamado HTML5 Shiv.

La solución para que IE aplique CSS

Afortunadamente alguien descubrió un hack muy sencillo que permite aplicar reglas CSS a elementos que el IE no reconoce. Sólo tenemos que crear explícitamente el elemento con document.createElement( elemento ).

Es decir, que bastaría con crear los elementos que vayamos a usar en la cabecera de nuestro documento:

<!DOCTYPE html>
<html lang="es">
  <head>
     
     Ejemplo de HTML5 Shiv
     
  </head>
  ...

Como es un código de uso habitual y que sólamente necesitamos ejecutar cuando la página se cargue en un IE6, IE7 o IE8, lo mejor es separarlo en un fichero externo y cargarlo de forma condicional para IE<9 :





Es necesario colocarlo en la cabecera porque el Internet Explorer necesita reconocer los elementos para poder presentarlos.

Hay que tener en cuenta también que, por defecto, estos elementos nuevos para el IE van a ser elementos de línea. Si queremos que sean elementos de bloque ( en la mayoría de los casos es lo normal ) tenemos que darles este estilo explícitamente en nuestro CSS:


header,nav,article,footer,
section,aside,figure,figcaption{display:block}

Utiliza el script ‘oficial’

El script html5shiv.js mantenido en GitHub por Alexander Farkas se ha convertido en el estándar de facto para utilizar los elementos de HTML5 en Internet Explorer anteriores al IE9.

Es recomendable utilizarlo porque soluciona otros problemas relacionados que se han ido encontrando con el tiempo ( problemas al imprimir y al manejar dinámicamente las etiquetas con innerHTML ).

Si sólamente necesitas dar estilo a los elementos, el código que hemos descrito antes puede servirte, pero utilizando el script html5shiv.js sabes que tienes cubiertos todos los problemas relacionados que se han encontrado hasta ahora y que ha sido probado por muchos miles de personas. Estas son las ventajas del software libre y de la colaboración.

Nota: Este script viene también incluido en la popular librería Modernizr, no es necesario incluirlo a parte.

Fuentes:
HTML5 Shiv
The Story of the HTML5 Shiv

miércoles, 19 de septiembre de 2012

Compresión HTTP

En un post anterior hablaba de que habilitar la compresión HTTP es una acción sencilla que puede mejorar enormemente el rendimiento de nuestra web. El funcionamiento está especificado en el estándar HTTP1.1 y forma parte del protocolo que implementan los navegadores y los servidores.

El concepto es muy sencillo: el servidor comprime automáticamente los datos antes de enviarlos y el navegador los descomprime cuando los recibe. El proceso de compresión y descompresión es rapidísimo. Nos permite ahorrar ancho de banda y mejorar los tiempos de respuesta al transmitir menos datos.

¿Y si algún navegador no soporta la compresión?

Todos los navegadores modernos lo soportan (incluso IE6), pero también existen otro tipo de clientes que pueden no implementarlo, como algunos robots de búsqueda o algunos browsers muy simplificados. En cualquier caso esto no supone ningún problema. Los navegadores que no soportan la compresión HTTP van a recibir los ficheros sin comprimir.

Cada vez que el navegador hace una petición al servidor, le indica, mediante una cabecera HTTP, que puede recibir contenido comprimido. El servidor, si está configurado para comprimir los datos, los enviará comprimidos y con una cabecera indicando el tipo de compresión aplicada.
Si el navegador no indica que puede manejarlo, el servidor nunca va a comprimir los ficheros
Este sería un ejemplo (simplificado) de la petición HTTP del cliente:

GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate

Vemos como el navegador indica al servidor que acepta contenido comprimido y le pasa una lista de formatos que puede descomprimir. En este caso gzip y deflate.

El servidor, en su respuesta con el contenido, incluirá las siguientes cabeceras (entre otras):

HTTP/1.1 200 OK
Date: Wed, 19 Sep 2012 13:42:14 GMT
Server: Apache/2.0
Content-Encoding: gzip
Content-Length: 1285

Indica al navegador que el contenido ha sido comprimido con gzip. Si la compresión HTTP no estuviera habilitada no incluiría la cabecera Content-Encoding y lo enviaría sin comprimir. El navegador, al no recibir la cabecera, sabría que no necesita descomprimir los ficheros.

No todos los ficheros se deben comprimir

El servidor web requiere una configuración para poder comprimir el contenido. Ademas de habilitar la funcionalidad debemos tambien indicar qué tipo de ficheros ( MIME type ) deben comprimirse, porque no todos lo necesitan.

Normalmente se configura sólo para ficheros de tipo texto ( JS, CSS, HTML, JSON, etc ). Las imágenes (JPG, PNG ) y el contenido multimedia ya están en formatos comprimidos y no ganamos nada comprimiendolos de nuevo. Estaríamos añadiendo carga de procesador tanto en el servidor como en el cliente sin ningún beneficio.


domingo, 16 de septiembre de 2012

CSS: Algunas curiosidades de 'margin collapsing'

Lo que en CSS se llama margin collapsing consiste en que cuando los margenes verticales de dos elementos de bloque se tocan, se fusionan en uno solo, del tamaño del mayor.

No encuentro una buena traducción en español así que hablaremos de margenes que se fusionan o se combinan.

Este comportamiento hace que, por ejemplo, si definimos que los párrafos tengan un margen de 10px, queden separados exactamente 10 pixels y no 20, que sería la suma del margen inferior del primer párrafo y el margen superior del segundo.



Este es el caso más sencillo y resulta fácil de comprender, pero cuando el fusionado de márgenes ocurre con elementos anidados, da lugar a situaciones un poco más confusas.

Elementos anidados

La combinación automática de margenes también se puede dar con un elemento dentro de otro. Los margenes del elemento padre y el elemento hijo se fusionan en uno sólo.

Por ejemplo, tenemos el siguiente HTML:
Se trata símplemente de un DIV dentro de otro, cada uno con su margen. Si no hubiera combinación de márgenes, lo veríamos así:



 Como vemos en la imagen superior, los márgenes top y bottom de los dos elementos están en contacto, por lo que se van a fusionar quedando sólo un margen, el mayor de los dos ( en este caso el de 30px):

 

El DIV interior queda sin margen. El DIV exterior queda con un margen de 30px.

 Lo curioso de este caso es que aunque el DIV interior sea el que tiene el margen más grande, el margen resultante se va a aplicar al DIV exterior. Cambiemos los márgenes en el HTML:
Si los margenes no se fusionan lo veríamos así:



 Igual que antes, tenemos dos DIVs cuyos márgenes están en contacto, por lo tanto se fusionan y queda un sólo margen del valor más grande.



 La conclusión es que el margen resultante se aplica siempre al elemento más externo que participa en la combinación. El DIV naranja siempre se queda sin margen. No importa cuál sea el valor que prevalece, se aplica siempre al elemento exterior.

¿Cómo evitamos que ocurra?


Es importante resaltar que para que se produzca esta combinación de márgenes, estos se tiene que tocar. Podemos evitar que ocurra si hay algo entre ellos. Basta con poner un borde o un *padding* que los separe:



 Además, cuando los elementos de bloque tienen alguna de las siguientes características, no se produce la combinación de márgenes:

  • Elementos flotantes
  • Elementos con posición absoluta
  • Elementos inline-block
  • Elementos con overflow diferente de visible. Sus márgenes no se fusionan con elementos hijo
  • Elementos con clear


References:
CSS - Auto-height and margin-collapsing
Understand CSS margins collapsing
CSS Tutorial - Margins Collapsing
Collapsing margins

viernes, 14 de septiembre de 2012

Nunca habrá CSS4

Muy clarificadora esta entrada, del blog de un miembro del grupo de trabajo de CSS en el W3C, sobre el nuevo formato modular de los estándares de CSS y porqué no tiene sentido hablar de CSS4.

CSS2.1 fué el último estandar que definía una versión completa de CSS. El grupo se dio cuenta de que continuar con versiónes monolíticas era un proceso muy lento y muy dificil de mantener. Decidieron dividirlo en módulos independientes que evolucionan a su propio ritmo y tienen su propio número de versión ( Selectors, Colors, Backgrounds and Borders, Media Queries, etc ). Cada uno de estos módulos es o será un estándar (una recomendación, para hablar con propiedad).

CSS3 es en realidad un nombre genérico para referirse a CSS posterior a CSS2.1 pero no podemos hablar de CSS de nivel 3 como una recomendación del W3C completa. Algunos módulos han empezado en el nivel 3 (si se basan en CSS2.1), otros han empezado en el 1 y otros van ya por el 4. Todos ellos forman parte de CSS3.

miércoles, 12 de septiembre de 2012

4 puntos sencillos para hacer nuestra web más rápida

Chris Coyer explica en el vídeo "Let’s Do Simple Stuff to Make Our Websites Faster" cuatro puntos importantes y sencillos para mejorar el rendimiento de nuestra web:
  1. Activar siempre la compresión HTTP  en el servidor.
  2. Cachear siempre que sea posible, tanto en el cliente como en el servidor.
  3. Optimizar las imágenes. Hay algunas herramientas especificas para esto.
  4. Combinar nuestros JS y CSS en el menor número de ficheros posible.
Aproximadamente el 80% del retardo que percibe el usuario final es debido a tareas de front-end, por lo tanto, es muy importante prestar especial atención a la optimización de la parte cliente (fuente: Steve Souders).

Estos cuatro puntos requieren muy poco esfuerzo y pueden tener un impacto enorme en el rendimiento de nuestra aplicación web.

Con el punto 1 hacemos que el servidor comprima los ficheros antes de enviarlos. El navegador los descomprime cuando lo recibe. Este proceso es rapidísimo y, en general,  siempre es mejor habilitar esta opción en el servidor.

Por otro lado, cachear, especialmente en el cliente, es importante porque la petición/envío HTTP de ficheros es lo más lento.
"La petición HTTP más rápida es la que no se hace"
Este es también el objetivo del último, evitar peticiones HTTP innecesarias al concatenar varios ficheros javascript o css.

domingo, 9 de septiembre de 2012

CSS: Pseudo-clases y pseudo-elementos

Los pseudo-elementos y las pseudo-clases se definen para poder aplicar estilos en algunos elementos, o partes de un elemento, en casos especiales que no quedan cubiertos con los selectores habituales. Nos permiten seleccionar, por ejemplo, la primera letra de una línea, la primera línea de un párrafo, un link cuando ya ha sido pulsado, etc.
La sintaxis es la misma en los dos casos:

 a:visited {
 /*esto es una pseudo-clase*/
 }

p:first-line {
 /*esto es un pseudo-elemento*/
 }
 
CSS3 introduce una diferencia, recomienda utilizar “::” para los pseudo elementos, para diferenciarlos de las pseudo clases:

 a:visited {
 /*esto es una pseudo-clase*/
 }

p::first-line {
 /*esto es un pseudo-elemento*/
 }
 
De todas formas el mismo estándar especifica que los navegadores deben continuar soportando la notación anterior para evitar problemas de compatibilidad con código CSS antiguo, al menos para los pseudo-elementos definidos en CSS1 y CSS2 ( :first-line, :first-letter, :before and :after ).

pseudo-clases

Seleccionan un elemento que cumple alguna condición o estado determinado, por ejemplo un link cuando el ratón está encima ( a:hover) o un elemento cuando es el primero dentro del elemento padre ( li:first-child). Podría ser similar a aplicar una clase temporal a un elemento en un estado determinado.

Pseudo-clases especificas para enlaces :link y :visited

 
 a:link { color: blue; }
 
Aplica a todos los enlaces que todavía no han sido visitados. Aparecerían en color azul.
 
 a:visited { color: red; }
 
Aplica a los enlaces que ya han sido visitados. Aparecerían en color rojo.

Pseudo-clases dinámicas :hover, :active, :focus

Permiten cambiar el estilo en respuesta a alguna acción del usuario. Se utilizan a menudo en los enlaces, como las anteriores, pero pueden utilizarse en otros elementos.
 
 button:hover { color: green; }
 
Define un estilo que se muestra mientras el cursos está sobre el elemento. Es muy utilizado en enlaces y botones para hacer un ligero cambio de estilo que muestra claramente sobre que opción del menú nos encontramos.
 
 a:active { color: orange; }
 
Define un estilo diferente durante el momento en el que el elemento está siendo pulsado.
input:focus{ background-color:yellow; }
Se aplica a un elemento cuando recibe el foco.

:first-child

Selecciona un elemento sólo cuando es el primer hijo del elemento padre. Por ejemplo, la siguiente declaración:
p:first-child { background:yellow; }

Se aplicará a todos los párrafos que sean el primer elemento dentro de un contenedor. Si tenemos varios divs para dividir la página en columnas, los primeros párrafos de cada columna aparecerán en amarillo.

Para hacer que el primer elemento de una lista aparezca siempre en rojo:
li:first-child{ color: red; }

Lenguaje :lang

CSS nos permite seleccionar un elemento basandonos en el lenguaje que se ha definido mediante el atributo lang ( lang=“es”). Si tuvieramos dos párrafos definidos de la siguiente forma:
 

Esto es una casa

This is a house

Podríamos seleccionarlos y darles estilos diferentes:
 
p:lang(es) { color: red; }
p:lang(en) { color: blue; }

Pseudo-classes de CSS3

El nuevo estándar añade una cantidad importante de pseudo-clases nuevas a las que dedicaré un post en detalle más adelante ( :target, :root, :empty, :only-child, :only-of-type, :last-child, :nth-child, etc).

pseudo-elementos

Permiten seleccionar ciertos ‘elementos virtuales’ que no existen como tales en el DOM, por ejemplo la primera linea de texto en un párrafo ( p:first-line) o la primera letra de una línea (p:first-letter).

:first-line y :first-letter

Permiten seleccionar la primera línea de un elemento y la primera letra de un elemento, respectivamente.

p:first-line { color: gray; }
p:first-letter { text-transform: uppercase; }
Estas dos reglas CSS harían que la primera línea de cada párrafo fuera siempre de color gris y que la primera letra de cada párrafo aparezca en mayúsculas.

Estos dos pseudo-elementos sólo pueden aplicarse a elementos de bloque y a celdas de una tabla. No pueden aplicarse a un span, por ejemplo.

:before y :after

Se utilizan para insertar contenido, generado en nuestro CSS, al principio o al final de un elemento existente. Podemos también definir el estilo de este nuevo contenido dentro de la misma regla.

La propiedad content sirve para definir el contenido nuevo que se añade.

Por ejemplo, si tenemos una lista de artículos, podriamos utilizar el siguiente código CSS para colocar una indicación de NEW delante y detras de los elementos nuevos:

 
li.new:before {
    content: "NEW - ";
    background-color: green;
}

li.new:after {
    content: " - NEW";
    background-color: green;
}

Todos los elementos de la lista a los que coloquemos la clase “new” apareceran con ese texto extra para llamar la atención.

Cuidado al colocarlos separados por comas

Al utilizar estos elementos en nuestra hoja de estilos conviene tener en cuenta que, si los colocamos en una regla de CSS junto con otros selectores, separados por comas, el navegador que no soporte el pseudo-elemento ignorará los siguientes elementos de la lista, sin aplicarles el estilo.

En el siguiente ejemplo:

p:last-child, p.special {
 /* mis estilos */
}

IE8 no soporta last-child ( de CSS3) por lo que salta al final de la lista y tampoco aplica el estilo a los párrafos p.special.

References:
W3C CSS2 Pseudo-elements and pseudo-classes

miércoles, 5 de septiembre de 2012

CSS: Para qué sirve vertical-align

Probablemente todos hemos intentado alguna vez utilizar vertical-align para centrar verticalmente un contenido ( por ejemplo, un texto ) dentro de un div.


En la imagen anterior, aunque apliquemos vertical-align: middle al div contenedor o al bloque de texto, no va cambiar nada. En los dos casos el navegador ignora esta propiedad porque no aplica a esta situación.

Entonces, ¿para qué sirve vertical-align?

Esta propiedad se define en el estándar CSS2 del W3C como aplicable sólo a elementos inline y table-cell. Esta es la razón por la que no funciona para centrar el ejemplo anterior, porque no puede aplicarse a elementos de bloque.

Además, por si el nombre no era ya bastante confuso, la propiedad tiene diferente significado según se aplique a elementos inline o a elementos con display: table-cell, por lo que veremos los dos casos por separado.

Los valores que puede tomar son:
Valores: baseline | top |  bottom | text-top | text-bottom | middle | sub | super | <porcentaje %> | <longitud (px, cm, etc)> | inherit

vertical-align para elementos inline

Sirve para establecer la posición vertical de un elemento con respecto a la línea en la que se encuentra.
En la siguiente figura podemos ver 6 líneas imaginarias que son importanes para entender lo que significa cada uno de los valores de esta propiedad:


Las líneas top y bottom quedan siempre por encima y por debajo, respectivamente, de todo el contenido. Si no hay imágenes, estas líneas pueden coincidir con text-top y text-bottom.

Las líneas text-top y text-bottom quedan por encima y por debajo, respectivamente, del punto más alto (o bajo) del texto, incluyendo símbolos y acentos.

La línea middle se coloca justo en el centro de la altura de las letras minúsculas (se toma la letra ‘x’ como referencia).

Baseline es la línea sobre la que se apoya el texto.

En los siguientes ejemplos veremos cómo se alinea una imagen y un texto (en gris) con respecto a una línea de texto sin modificar (en negro), al cambiarles el vertical-align.

El HTML sería este:
 
  This is the default linedefault alignment 
  


Al elemento <p> le pondremos un borde azul, a los dos elementos que modificaremos con el vertical-align les ponemos borde naranja y al texto por defecto del párrafo, un borde gris.

Sin ningún vertical-align, se vería así:


Para cada uno de los valores posibles de la propiedad tendríamos:

baseline


Este caso es igual a la imagen sin vertical-align que veíamoss arriba, porque este es el valor que se muestra por defecto. La baseline del texto en gris y la base de la imagen se alinean con la baseline del texto sin modificar del elemento padre ( el <p>).


top


La parte superior de los elementos se alinean respecto al punto más alto de la línea ( sea imagen o texto ).

 

bottom


La parte inferior de los elementos se alinean respecto al punto más bajo de la línea ( sea imagen o texto ).

text-top


Los elementos se alinean con el punto más alto del texto. Es importante tener en cuenta que quizá la letra que tiene el punto más alto en nuestra fuente no aparece en nuestro texto. Aun así, la colocación se realiza respecto a ese punto más alto. Las letras más altas normalmente son las mayúsculas acentuadas.


text-bottom


Caso apuesto a text-top, los elementos se alinean con la parte más baja del texto. No es lo mismo que baseline porque en este caso vamos más abajo, incluyendo las líneas que bajan por debajo de la línea de base ( letras como p, j, g, etc).


middle


En este caso el navegador trata de alinear el centro vertical de los elementos con el centro del texto del padre ( el párrafo <p>).

 Lo que los navegadores hacen es alinear el centro vertical del elemento (de su caja) con la línea de base ( baseline ) del texto del padre más la mitad de la altura de la x. Visualmente puede no ser exactamente lo que esperamos porque, al no haber letras con partes descendentes, parece un poco descentrado.

super


Se utiliza para posicionar algo como superíndice.

sub


Se utiliza para posicionar algo como subíndice.

porcentaje


Eleva la posición del elemento en el porcentaje indicado. Es un porcentaje de la altura de la caja del propio elemento que tiene el vertical-align, por eso la imagen y el texto están a alturas diferentes. El valor puede ser negativo, en ese caso la posición baja. El valor 0% corresponde a la baseline.

Longitud en pixels o otras unidades


Eleva la posición del elemento en las unidades indicadas (px, cm, etc). El valor puede ser negativo, en ese caso la posición baja. El valor 0 corresponde a la baseline.

vertical-align para celdas de una tabla o elementos con display:table-cell

En el contexto de celdas de una tabla esta propiedad tiene un significado diferente. Sirve para definir como se alinea verticalmente el contenido de una celda. En este contexto sólo aplican los valores middle, top y bottom.

Para celdas de una tabla ( <table> ) el valor por defecto es middle, pero para un div con display:table-cell, el valor por defecto es top.

La siguiente imagen muestra como queda un bloque de texto con cada uno de los valores: