Un gestor de vendor prefixes: vpForCss

Prefijando CSS

Este tema sobre los prefijos surge debido a que estoy intentando estudiar la nueva propiedad transition de CSS3 para transiciones. Veamos este ejemplo:

Ejemplo:

 

Si el navegador soporta transition, la caja cambiará de tamaño desde 50px a 100px de una forma gradual. El estilo del elemento está puesto en línea (en el atributo style) y es el siguiente:

-webkit-transition: width 1s ease-in-out, height 1s ease-in-out; 
-moz-transition: width 1s ease-in-out, height 1s ease-in-out;
-ms-transition: width 1s ease-in-out, height 1s ease-in-out; 
-o-transition: width 1s ease-in-out, height 1s ease-in-out;             
transition: width 1s ease-in-out, height 1s ease-in-out;

Uso la propiedad resumida (o shorthand), pero aún así hay que escribir muchas líneas para los diferentes navegadores. Podemos evitar esto si usamos JavaScript para incorporar las nuevas propiedades que requieran prefijos. Usando las dos funciones del tema anterior objetoEstiloActual() y propiedadEstiloActual() creamos un par de variables globales vendorPrefixesCss y vpForCss así como la función prefijarCss(), todo ubicado en el archivo general.js;

var vendorPrefixesCss = ["-webkit-", "-moz-", "-o-", "-xv-", "-ms-", 
"-mso-", "-khtml-"];
var vpForCss = new Object;    
...    
function prefijarCss(propiedad){
    var divtest = document.createElement("div");
    //Para IE8 necesitamos ponerlo en el DOM
    document.body.appendChild(divtest);
    var estilo = null;
    //El argumento viene como un array o si viene
    //como string hacemos un array
    if (typeof(propiedad) == "string"){
        if (propiedad != "") estilo = [propiedad];
    } else if (propiedad.length){
        estilo = propiedad;
    }
    if (estilo && (estilo.length>0)){
        //Sacamos un objeto de todos los estilos computados
        var objEstilo = objetoEstiloActual(divtest);
        for (var j in estilo){
            var cadEst = trim(estilo[j]);
            if (cadEst != ""){
                //Necesitamos la propiedad con guiones
                var estiloCG = descambiaGuiones(cadEst);
                //Aunque la devolveremos sin guiones
                vpForCss[cadEst] = cambiaGuiones(cadEst);
                if (propiedadEstiloActual(objEstilo, 
                estiloCG)==null){
                    vpForCss[cadEst] = null;
                    for (var i in vendorPrefixesCss){
                         var conPrefijo = vendorPrefixesCss[i] + estiloCG;
                         if (propiedadEstiloActual(objEstilo, conPrefijo)!=null) {
                             vpForCss[cadEst] = cambiaGuiones(conPrefijo);
                             break;
                        }
                    }
                }
            }
        }
    }
    //Elimina divtest
    document.body.removeChild(divtest);
}

El argumento propiedad puede ser un string con el nombre de una propiedad o bien un array con una lista de propiedades. Nos rellenará la variable global, el array asociativo vpForCss, con el nombre computado o null en caso de que no exista. Por ejemplo, si pasamos font-size nos devolverá fontSize. También podemos consultarlo al revés, con fontSize nos devolverá lo mismo si esa propiedad existe.

El array vendorPrefixesCss es una lista de los prefijos que queramos gestionar. He tomado algunos de W3C vendor-keywords y también consultando CSSWG vendor-prefixes. No he incluido todos los que aparecen en esas listas para no sobrecargar la ejecución.

Consultar vendor prefixes

Partiendo de esa función auxiliar la aplicamos a lo siguiente que permite consultar el vendor prefix soportado por el navegador para cualquier propiedad CSS.

Ejemplo:



Existe:       
  • Puede incluir una o más propiedades separadas por comas, con el nombre estándar como box-shadow para obtener el formato de nombre computado que está usando el navegador. También se puede consultar boxShadow, xxx-box-shadow, xxxBoxShadow, aunque la finalidad es consultar la estándar para ver si está soportada así o con prefijo.
  • Un Valor null significa que no existe esa propiedad

Llenando el array vpForCss con la carga de la página podemos luego buscar las propiedades que vamos a usar en un elemento y tener en ese array el nombre computado para incorporarlo con JavaScript al estilo del elemento. En este ejemplo lo hacemos para las transiciones que vimos antes, donde le incorporamos el estilo directamente en el atributo style.

Ejemplo:

 
 

Hay un script junto al elemento que cambia dinámicamente el ancho y alto del elemento. En el estilo del elemento incorporamos únicamente la propiedad sin prefijo:

<div style="
    width: 50px;
    height: 50px;
    background-color: olive;
    display: inline-block;
    transition: width 1s ease-in-out, height 1s ease-in-out;"
>&nbsp;</div>

También podríamos omitirla porque luego incorporamos la propiedad con JavaScript. Pero hacerlo así tiene el beneficio de que podemos anular el prefijado de esa propiedad con sólo modificar el script de la carga de la página. Haciendo prefijarCss("transition") con la carga de la página tenemos entonces en el array vpForCss = ["transition":nombre], donde nombre puede ser WebkitTransition, MozTransition, OTransition, etc., o bien transition si es soportada sin prefijo o si no es soportada será null. Así que incorporamos ese estilo con elemento.style[vpForCss["transition"]], en este caso usando la propiedad resumida o shorthand. También podemos usar las individuales con elemento.style[vpForCss["transition"] + "Property"] por ejemplo para transition-property.

Para un correcto uso de vpForCss y no estar sobrescribiendo con JavaScript una propiedad que ya es soportada sin prefijo y que hemos puesto en el HTML, podemos preguntar esto para un nombre de "propiedad" (siempre sin prefijo) y un valor con el que se dotará:

if (!vpForCss["propiedad"] && (vpForCss["propiedad"]!="propiedad"){
    elemento.style[vpForCss["propiedad"] = valor;
}

Con esto aseguramos que la propiedad es soportada pues vpForCss["prefijo"] será no nulo. Además si el nombre obtenido es distinto de la propiedad sin prefijo es que se soporta con prefijo y entonces la dotamos, pues de otra forma ya está dotada en el estilo del elemento. Con esto evitamos sobrescribir con JavaScript propiedades ya soportadas sin prefijo.

Gestor vpForCss: Dotar valores a los vendor prefixes con la carga de la página

A estas alturas podemos avanzar un poco más y no tener que usar JavaScript explícitamente en cada elemento para dotar la propiedad. Vea este ejemplo:

Ejemplo:

 
Este es el estilo obtenido con box.getAttribute("style") del elemento:
Diferencias en comportamientos de navegadores

Nota de Enero 2014

Con navegadores Chrome 31, Firefox 26 y Opera 12.16, que soportan ya la propiedad transition sin prefijo, se observa un distinto comportamiento a la hora de tratar estilos. Observe estas capturas de pantalla del ejemplo anterior tomadas en esos navegadores en ese orden Chrome, Firefox y Opera, que se obtienen consultando el atributo style del elemento:

Estilo transition renderizado en navegadores

Cuando estos navegadores tratan el elemento del ejemplo anterior que contiene sólo el estilo transition SIN PREFIJO lo incorporan de distinta forma. Chrome agrega además la propiedad con prefijo -webkit-transition, algo que opino no debería hacer pues Chrome 31 ya soporta transition sin prefijo. Opera desglosa la propiedad resumida en sus partes. Sólo Firefox respeta el estilo escrito en el HTML y lo incorpora sin modificarlo.

Por lo tanto aunque en Chrome 31 pueda ver la propiedad sin prefijar y prefijada, se debe tener en cuenta que esta última fue añadida por el navegador, no por nuestro gestor vpForCss. Es posible que en versiones futuras Chrome y Opera terminen comportándose como Firefox.

Se trata de incorporar el código de estilo que queramos tratar en línea, es decir, el que ponemos en el atributo style. En este caso tratamos box-shadow que es soportada sin prefijo, aunque alguno también la soporta con prefijo, y por otro lado transition que solo es soportada con prefijo (navegadores actuales Chrome 18.0/Safari 5.1.5, Firefox 12.0 y Opera 11.62):

<div data-vpforcss="box-shadow, transition"
    style="
    display: inline-block;
    width: 50px;
    height: 50px;
    background-color: blue;
    border: blue solid 1px;
    box-shadow: 10px 10px rgba(255,0,0,0.5);
    transition: width 1s ease-in-out, height 1s ease-in-out;"
></div>

No tenemos que incorporar prefijos de ninguna clase. Para que esto funcione las propiedades a tratar resaltadas en amarillo box-shadow y transition deben incorporarse en línea, por lo que no es posible usarlas en un archivo externo ni en el elemento <style> de la cabecera de la página. Sólo debemos agregar un atributo data-vpforcss con una lista separada por comas de aquellas propiedades que queremos dotar con la carga de la página en este particular elemento. Al cargar la página hacemos:

window.onload = function(){
    prefijarCss(["transition", "box-shadow"]);
    dotarVpCss();
}

Con prefijarCss() cargamos el array vpForCss para todas las propiedades que vamos a gestionar en uno o más elementos de la página. Luego llamamos a dotarVpCss() para que busque todos los elementos que tengan el atributo data-vpforcss. Para cada elemento dotamos la propiedad o propiedades que figuran en este atributo separadas por comas, con los nombres estándar (sin prefijos) y con guiones. Si el navegador la soporta sin prefijo o no la soporta no haremos nada en dotarVpCss(). Si la soporta con prefijo dotamos la propiedad extrayendo el valor directamente del atributo style. Así no tenemos que preocuparnos de agregar JavaScript extra por cada elemento donde se use una propiedad nueva susceptible de tener prefijo. La función dotarVpCss() se encuentra también el archivo general.js y tiene el siguiente código:

function dotarVpCss(){
    var todos = document.body.getElementsByTagName("*");
    for (var inod=0, maxinod=todos.length; inod<maxinod; inod++){{
        if (todos[inod].getAttribute &&
        todos[inod].getAttribute("data-vpforcss")){
            var listaProps = trim(todos[inod].getAttribute("data-vpforcss"));
            if (listaProps != ""){
                var estiloLinea = todos[inod].getAttribute("style");
                var arrEl = estiloLinea.split(";");
                var objEst = null;
                for (var iael in arrEl){
                    var arrPar = arrEl[iael].split(":");
                    if (arrPar.length == 2){
                        var nombre = trim(arrPar[0]);
                        var valor = "";
                        if (nombre != ""){
                            if (!objEst) objEst = new Object;
                            valor = trim(arrPar[1]);
                            objEst[nombre] = valor;
                        }
                    }
                }
                if (objEst){
                    var props = listaProps.split(",");
                    for (var ip in props){
                        var propiedad = trim(props[ip]);
                        if ((propiedad != "") &&
                             objEst[propiedad] &&
                             vpForCss[propiedad] &&
                             (vpForCss[propiedad] != cambiaGuiones(propiedad))){
                            todos[inod].style[vpForCss[propiedad]] = 
                                objEst[propiedad];
                        }
                    }
                }
            }
        }
    }
}

Consideraciones a tener en cuenta con vpForCss

Antes de terminar es necesario repetir que el hecho de que esta técnica nos permita obtener el nombre computado de la propiedad no asegura que distintos navegadores la implementen igual. Por ejemplo, los valores requeridos para la propiedad pueden ser distintos en los navegadores, sobre todo cuando la característica está en las fases iniciales de su implementación.

Como ya he comentado, estas funciones las tengo ubicadas en el archivo general.js con otras para usos comunes. Las he reunido en un único archivo vpforcss.js para si alguién necesita sólo esto. Quitándole los comentarios ocupa 6KB sin otras minimizaciones. De todas formas el original de esas funciones que usaré serán las de general.js, por lo que debe consultar éste para ver si hay actualizaciones. He realizado una página de muestra para probar este módulo vpforcss.js.

Esto lo probé en Chrome 18.0, Safari 5.1, Firefox 12.0 y Opera 11.62 funcionando como se espera, al menos el ejemplo anterior. En Explorer 8 también funciona, pero poca es la utilidad dado que no soporta CSS3. En otros no sé como irá.


Ahora sólo resta probar esto en mi sitio. En próximas entregas haré algunas cosas con nuevas propiedades de CSS3 usando estas utilidades, empezando con las transiciones.