Eventos CSS con selectores por pseudoclase dinámica

Las pseudoclases dinámicas son E:hover{}, E:active{} y E:focus{} (entre otras), siendo "E" el elemento sobre el que se aplica. De lo que se trata es de aplicar estilo a un parte del documento que no está especificada en la estructura HTML. Se crea dinámicamente una clase según una ejecución dinámica en ese elemento. Estas pseudoclases pueden aplicarse a cualquier elemento que sea capaz de interactuar de alguna manera con él, como pulsando el ratón por ejemplo.

  • E:hover{} actúa al señalar un elemento previamente a que sea activado. Digamos que es como "pasar el ratón por encima".
  • E:active{} actúa cuando es activado.
  • E:focus{} actúa cuando obtiene el foco. Los elementos capaces de recibir el foco son los que también obedecen el evento onfocus como <a>, <input>, <select>, <textarea> y <button>.
En mi glosario XHTML+CSS puse que los elementos capaces de recibir el foco son <a>, <area>, <label>, <input>, <select>, <textarea> y <button>. Sin embargo el elemento <label> es una simple etiqueta y realmente quién recibe el foco es el control que tiene asociado.

Hasta aquí la teoría. Pero para seguir hay que saber cómo podemos interactuar con un elemento. Hagámos algunas pruebas con varios tipos de elementos. En el estilo incluido en la cabecera de esta página he puesto lo siguiente:

.ehover, .eactive, .efocus {
    border: blue solid 4px;
    background-color: transparent;
    margin: 0.5em;
    padding: 0.2em;
    }
.ehover:hover, .eactive:active, .efocus:focus {
    border-color: red;
    background-color: yellow;
    }
.evento-script {
    border: green solid 4px;
    background-color: transparent;
    margin: 0.5em;
    padding: 0.2em;        
    }

Crearemos tres grupos de clases de elementos: class="ehover", class="eactive" y class="efocus" usando <span>, <a>, <input type="text">, <button>, <fieldset> y un <td> dentro de una tabla con esa única celda. Estos elementos HTML no tiene ningún script sino que las acciones dinámicas que experimentamos suceden exclusivamente con el estilo de las pseudoclases dinámicas, es decir, con eventos CSS. Luego aplicaremos esas pseudoclases respectivas a cada grupo que modificará el color de fondo a amarillo y el borde a rojo. También usamos un elemento de la clase .evento-script con borde verde:

<ELEMENTO class="evento-script" 
onX="simulaEvento(this)"
onY="finalizaEvento(this)"
onZ="finalizaEvento(this)" 
>...

El "ELEMENTO" será un <span> para los dos primeros ejemplos y un <input> para el ejemplo del evento al recibir el foco. Estos portan un evento "onX" que llama a simulaEvento(this) y los otros dos "onY", "onZ" (sólo para el segundo ejemplo) llaman a finalizaEvento(this) para finalizar la ejecución de la simulación. Se trata de experimentar el equivalente con Javascript. Los valores "onX", "onY" y "onZ" a usar son:

PseudoclaseonXonYonZElemento
E:hoveronmousemoveonmouseout<span>
E:activeonmousedownonmouseuponmouseout<span>
E:focusonfocusonblur<input>

El código de estas funciones script está en la cabecera de esta página y es el siguiente:

function simulaEvento(este){
    este.style.borderColor = "red";
    este.style.backgroundColor = "yellow";
}
function finalizaEvento(este){
    este.style.borderColor = "green";
    este.style.backgroundColor = "white";
}

El elemento vínculo lo ponemos como <a href="javascript:" class="...>... tal que su atributo href parece que ejecuta Javascript pero realmente no hace nada. Esta es una forma de evitar que al pulsar el vínculo la página se desplace al inicio de la misma, como sucedería si ponemos el separador "#" del vínculo interno.

La pseudoclase dinámica E:hover

Para la pseudoclase E:hover{} aplicada a los del grupo ehover tenemos lo siguiente:

Ejemplo:

<span>
<fieldset>
<td>
<span> con eventos onmousemove y onmouseout

Esta pseudoclase dinámica E:hover es similar a la combinación de eventos onmousemove y onmouseout, eventos que se activan al mover el dispositivo apuntador o ratón por encima del elemento y al salir del mismo. Los navegadores que estoy probando son Explorer 8.0 (IE), Google Chrome 12.0 (CH), Firefox 5.0 (FF), Safari 5.0 (SA) y Opera 11.5 (OP). Todos responden a este evento CSS con igual resultado.

La pseudoclase dinámica E:active

La pseudoclase E:active{} puede equipararse con el evento onmousedown que se activa cuando pulsamos con el ratón y mientras dure esa pulsación. Este evento hay que finalizarlo usando onmouseup y onmouseout, pues podemos salir del elemento manteniendo el botón del ratón pulsado y se quedaría con los colores modificados. Por lo tanto el evento CSS se ejecuta en y durante el momento que el elemento está siendo activado. En estos ejemplos los elementos tienen unos bordes gruesos pues a veces el elemento responde sólo pulsando el ratón en ese borde. Hay que recordar que los bordes ({border}) y rellenos ({padding}) forman parte del área activa del elemento, mientras que no lo son los márgenes ({margin}). En este caso también se observa el mismo comportamiento en los navegadores consultados.

Ejemplo:

<span>
<fieldset>
<td>
<span> con eventos onmousedown, onmouseup y onmouseout

La pseudoclase dinámica E:focus

La pseudoclase E:focus{} tiene un correspondencia en Javascript con el evento onfocus en combinación con onblur. El primero actúa cuando el elemento obtiene el foco y el otro cuando lo pierde. En este ejemplo hemos agregado algunos controles más de formulario, pero todos los siguientes se han incluido dentro de un elemento <form>:

Ejemplo:

<span>
<fieldset>
<td>

No esperaba que el <span> obtuviera el foco, pero también pensaba que Explorer no lo iba a aplicar a un elemento de celda o a un <fieldset> por lo que esto me sorprendió. Por otro lado era lógico pensar que los elementos capaces de recibir el foco como el vínculo <a> y los controles de formulario si funcionarían de forma igual en todos los navegadores. Pero lo relevante aquí es el comportamiento de Safari y Chrome para <a> que no reciben el foco cuando pulsamos con el ratón. Pero si le dotamos del atributo tabindex entonces si lo hacen: observe como con Safari o Chrome podemos pasar entre los dos elementos <a tabindex="...> con la tecla tabuladora.

El hecho de incluir los cuadros de control de formulario dentro de un <form> es que automáticamente se le asigna un tabindex dentro del formulario según la posición que ocupe. Esto para los controles <input>, <select>, <textarea> y <button>, puesto que para otros elementos que no sean estrictamente controles como <a> esta tabulación automática no funciona. Por eso es conveniente incluir el atributo tabindex en los ejemplos de las pestañas además de encerrar todo el conjunto en un <form>.

En todo caso si queremos usar un evento CSS para modificar algo en el estilo hemos de usar un E:focus. Este evento CSS mantiene las modificaciones durante todo el tiempo que el elemento mantenga el foco, pues los otros E:hover o E:active sólo lo hacen mientras estemos pasando el ratón encima del elemento o entre tanto se mantenga la tecla pulsada respectivamente.

Referencias CSS con selectores por contexto

La aplicación en CSS de las pseudoclases dinámicas en combinación con los selectores por contexto nos permiten obtener referencias a otros elementos. La selección por contexto nos dice que si "E" y "F" son dos elementos (o selectores), se trata de seleccionarlos en función del contexto:

  • E F{}, que selecciona TODOS los elementos F descendientes (es decir, incluidos) en el elemento E. Por lo tanto los elementos descendientes serán los hijos, nietos, etc.
  • E>F{}, que selecciona un elemento F que es hijo de E, por lo que no seleccionará los nietos ni siguientes.
  • E+F{}, que selecciona los hermanos E y F incluidos dentro de un mismo elemento padre.

Para los ejemplos de pestañas, el elemento "E" sería el que recibe el evento del foco. Así tendríamos que buscar la referencia "F" que sería el contenedor de esa pestaña para aplicarle las propiedades que hacen que se muestre. Es importante entender que en todo caso las referencias son a hijos o hermanos del elemento, pero no podemos referenciar cualquier otro elemento sin parentezco.

Yo no he visto con CSS-2.1 la posibilidad de referenciar un elemento cualquiera de la página sin que tenga que ser hijo o hermano tras recibir un evento CSS. Por ejemplo, algo como:

a.abc:focus ? div.xyz {...}

Este operador ? sería un hipotético selector de tal forma que cuando el elemento <a class="abc"> recibiera el evento entonces se seleccionaría un elemento <div class="xyz"> que podría estar en cualquier sitio del documento. Pero no soy un experto en CSS y es posible que algo similar pueda realizarse. Mientras tanto y para realizar nuestros ejemplos no me queda más remedio que usar alguno de los selectores anteriores.

Esquema de funcionamiento del contenedor de pestañas con elementos vínculo

Veámos un ejemplo simple con un <form> que incluye un vínculo <a> (elemento "E") y un interior con un elemento <ins> (elemento "F"), usando la selección por contexto de padre a hijo E F{}:

Se trata de un esquema básico de contenedor de pestañas donde se resaltan los bordes de cada parte de la estructura para visualizarlo. El html es el siguiente:

<form>
    <!--  primera pestaña -->
    <a href="javascript:" class="ref1" tabindex="10"
    >primera<span>PRIMER INTERIOR</span></a>
    <!--  segunda pestaña -->
    <a href="javascript:" class="ref1" tabindex="11"
    >segunda<span>SEGUNDO INTERIOR</span></a>
    <!--  fondo -->
    <div class="ref1-fondo">&nbsp;</div> 
</form>
Ver la nota sobre Pseudo-protocolo javascript: en atributos href de un vínculo donde se expone como evitar usar href="javascript:" para mejorar la usabilidad de la página.

El estilo CSS es el siguiente

a.ref1 {
    background-color: gray;
    border: green dotted 1px;
    text-decoration: none;
    outline: none;
    }
a.ref1 span {
    background-color: rgb(235, 235, 225);        
    display: block;
    position: absolute;
    z-index: -1;
    color: black;
    border: blue dashed 1px;
    } 
a.ref1:focus {
    background-color: rgb(235, 235, 225);
    color: blue;
    }    
a.ref1:focus span {
    z-index: 1;
    }
div.ref1-fondo {
    width: 20em;
    height: 6em;
    background-color: rgb(235, 235, 225);
    border: orange solid 1px;
    }

Con a.ref1 span estamos seleccionando (referenciando) todos los elementos <span> que son hijos del vínculo <a class="ref1">. En este caso sólo hay uno. Forzamos el <span> a elemento de bloque con display: block. También hay que posicionarlo de forma absoluta para que "abandone" su ubicación inicial y que aparente "colgar" directamente del contenedor <form> exterior.

Cuando la pestaña recibe el evento foco a.ref1:focus le cambiamos su apariencia con un color de fondo más claro y otro color de letra. A continuación con ese mismo evento referenciamos el contenido con a.ref1:focus ins para que pase a una capa más alta con z-index: 1. Este esquema básico necesita algunos retoques, como ocultar el borde que contacta entre la pestaña y el interior y arreglar el resto de bordes. También el hecho de que cuando no hay ninguna pestaña activada debería salir otro contenido que lo reflejase. Todo esto puede verlo en el ejemplo en ejecución del tema anterior.

La desventaja de este esquema frente a los de Javascript son:

  • Cuando no hay foco en ninguna pestaña se queda abierto el contenido de la última. En la versión completa del tema anterior dispongo de una pestaña extra que haría de contenido de fondo para esta incidencia. Pero es frustrante que no puedas elegir una pestaña de inicio y que cuando abandone el foco se quede ese contenido de fondo abierto.
  • No podemos incluir elementos vínculo dentro del contenido interior, pues estarían anidados con el vínculo exterior y el DTD no lo permite.

Esquema de funcionamiento del contenedor de pestañas con elementos cuadro de texto

A modo de ejercicio en lugar de vínculos para las pestañas probaremos usando elementos de cuadros de texto, los <input type="text"> que también son capaces de recibir el evento CSS de foco. Usaremos en este caso la selección por contexto de hermano a hermano E+F{}.

Ejemplo:

PRIMER INTERIOR
SEGUNDO INTERIOR
 

Este esquema no funciona adecuadamente en Internet Explorer 8, pues tras pulsar una pestaña se actualiza el interior sólo si movemos el ratón (a veces) o si actualizamos la página. En cambio no ofrece problemas con los navegadores Google Chrome 12.0, Firefox 5.0, Safari 5.0 y Opera 11.5. El HTML es el siguiente:

<form class="fref2">
    <!--  primera pestaña -->
    <input type="text" class="ref2" tabindex="10"
    value="primera" /><div class="dref2">PRIMER INTERIOR</div>
    <!--  segunda pestaña -->
    <input type="text" class="ref2" tabindex="10"
    value="segunda" /><div class="dref2">SEGUNDO INTERIOR</div>
    <!--  fondo -->
    <div class="ref2-fondo">&nbsp;</div>       
</form>

Ahora el contenedor con el interior de la pestaña no es hijo de ésta, pues <input> es un elemento vacío. Se trata entonces de un hermano <div>. El estilo es muy similar también al anterior caso:

form.fref2 {
    position: relative;        
    width: 20em;
    height: 10em;
    border: red dotted 1px;
    }      
input.ref2 {
    width: 4em;        
    background-color: gray;
    border: green dotted 1px;
    outline: none;                          
    cursor: pointer;
    }
div.dref2 {
    position: absolute;
    z-index: -1;
    background-color: rgb(235, 235, 225);
    height: 5em;
    border: blue dashed 1px;
    }
input.ref2:focus {
    border-bottom-color: rgb(235, 235, 225);
    background-color: rgb(235, 235, 225);
    color: blue;
    }
input.ref2:focus + div.dref2 {
    z-index: 1;
    }
div.ref2-fondo {
    z-index: 0;
    background-color: rgb(235, 235, 225);          
    border: orange solid 1px;
    height: 7em;
    width: 100%;
    }

El estilo se complica un poco pues hemos de posicionar relativamente el formulario para que se acomoden los posicionamientos absolutos de los <div>. Este esquema tiene las siguientes desventajas:

  • Como el caso anterior, no es posible tener una pestaña abierta por defecto tal como hacíamos con Javascript. Cuando todas pierden el foco se queda abierto el contenido de la última pestaña.
  • Internet Explorer 8 no funciona adecuadamente con la pseudoclase dinámica de foco en combinación con la selección por contexto.
  • Aunque ahora si podemos incluir vínculos dentro del contenido, el problema es que no podemos hacer click sobre ese contenido. Ello se debe a la desventaja primera pues la pestaña pierde el foco y se sitúa en la capa más alta el último contenido.
  • El texto de la pestaña no puede ser HTML, pues es el valor del atributo value de un <input>.