> Manuales > Taller de HTML5

Haz que tu sitio web se vea perfectamente en cualquier lugar.

Cuando nos ponemos a desarrollar un sitio web, nos interesa sin duda que se vea perfectamente en cualquier navegador… y si es posible, que se vea bien por mucho tiempo, incluso en versiones futuras de esos navegadores también. He ido recogiendo una serie de consejos, experiencias y buenas prácticas que te ayudarán a hacer que tus sitios web se vean de la mejor forma posible.

Un poco de historia

Actualmente todos los navegadores se desarrollan con un mismo objetivo: representar de manera óptima las páginas web siguiendo las especificaciones más recientes.

Pero no siempre ha sido así. En el pasado, la mayor parte de los navegadores implementaban funciones muy demandadas que aún no estaban sujetas a ningún estándar. Cada navegador iba a su aire, en temas, por ejemplo, como la posibilidad de establecer transparencias en CSS.

Internet Explorer, antes de la versión 8, entendía el siguiente código CSS: .transparent {
     /* Internet Explorer < 9 */
    width: 100%;
     filter: alpha(opacity=50);
}
Firefox tenía su propio atributo:

.transparent {
     /* Firefox < 0.9 */
     -moz-opacity:0.5;
}

Y también Safari:

.transparent {
     /* Safari < 2 */
     -khtml-opacity: 0.5;
}

Pero ahora, con CSS3 ya tenemos una forma unificada para definir la transparencia de un elemento:

.transparent {
     /* IE >= 9, Firefox >= 0.9, Safari >= 2, Chrome, Opera >= 9 */
     opacity: 0.5;
}

Desde luego podemos pensar que está muy bien que algunos navegadores se "adelanten" para soportar nuevas funcionalidades antes que nadie. Pero cuando estas funcionalidades aún no son estándar, lo que hacen es generar problemas a los desarrolladores, puesto que tenemos que tener en cuenta todas las distintas implementaciones que se hacen de cada funcionalidad.

El mismo markup

La mejor forma de estar seguros de que nuestras páginas se van a mostrar de manera óptima en todos los navegadores es, sin lugar a dudas, utilizar un código ("markup") soportado por todos los navegadores actuales. Hasta hace bien poco, este markup era HTML 4.01, un estándar de hace una década con funcionalidades bastante limitadas.

Hoy en día todos los navegadores están convergiendo en el HTML5, mucho más avanzado. Pero muchas de las nuevas especificaciones que caen dentro del término "HTML5" (incluyendo el propio markup HTML5, sus APIs, como DOM niveles 2 y 3, CSS3, SVG y EcmaScript 262) todavía están en desarrollo y sujetas a cambios.

Los fabricantes de navegadores están añadiendo soporte para funcionalidades de HTML5 de manera permanente, pero a ritmos notablemente distintos.

Firefox y Opera normalmente suelen adoptar nuevas especificaciones HTML5 con gran celeridad, aun sabiendo que algunas de ellas están en fases tempranas de desarrollo, que pueden cambiar o que presentan problemas de seguridad. 

Por un lado, a los desarrolladores les parece fenomenal poder tener la posibilidad de probar estas novedades. Pero una adopción prematura a menudo lleva a que los sitios web utilicen funcionalidades que afectan gravemente a las páginas a cada cambio de versión del navegador. Un ejemplo de esto es, por ejemplo que Firefox 4 ha desactivado Websockets al pasar de la Beta 7 a la 8 debido a los problemas de seguridad que plantea. Es una experiencia tremendamente frustrante, tanto para los usuarios como para los desarrolladores.

Chrome— que también está incorporando estándares HTML5 a buen ritmo- ha conseguido hace poco mosquear a la comunidad de HTML5 al anunciar que deja de dar soporte al conocido y extendido códec de vídeo H.264 para los elementos <video> de HTML5 y en su lugar soportará el estándar WebM, libre de derechos. Aunque no es mala decisión para los desarrolladores que actualmente tienen que pagar por las licencias de H.264, de nuevo introduce una variante más en sus planes, de modo que tienen que tener en cuenta en qué formatos de vídeo deberán codificar y almacenar sus contenidos para que éstos sean compatibles con la mayor gama posible de navegadores.

Microsoft no incorpora los estándares de manera tan rápida, pero colabora estrechamente con el W3C en la creación de paquetes de prueba. Con esta colaboración pretende:

Si quieres ver las últimas actividades desempeñadas en este terreno, echa un vistazo a la Preliminar de Plataforma Internet Explorer 10 que ya está disponible en IE Test Drive.

También puedes ver lo que hay publicado en HTML5labs, donde Microsoft tiene prototipos de especificaciones todavía inestables y en sus primeras fases, procedentes de organismos de estandarización como el W3C . En Improved Interoperability Through Standards Support puedes encontrar información detallada sobre el soporte que ofrece ahora mismo Internet Explorer 9 para algunas especificaciones de HTML5.

Pero desde el momento en que los nuevos estándares HMTL5 siguen siendo un blanco móvil –y la mayoría de usuarios de Internet no utilizan las últimas versiones de ningún navegador- el suministrar el markup correcto es ahora más importante que nunca.

Detección del navegador

Una manera de resolver las diferencias entre navegadores consiste en utilizar la detección del navegador. La forma más habitual de hacerlo es emplear Javascript para consultar la cabecera del agente de usuario:

<script type="text/javascript">
     if ( navigator.userAgent.indexOf("MSIE")>0 )
     {
         // run custom code for Internet Explorer.
     }
</script>

Pero haciéndolo así tenemos que resolver un par de inconvenientes.

El primero, que con una única comprobación damos por hecho que el navegador soporta una serie de funcionalidades. Un fallo en estas suposiciones y nuestro sitio web dejará de funcionar. Así que, como desarrollador, necesitas poder controlar de manera más precisa las funcionalidades soportadas por cada navegador.

EL segundo problema es que la comprobación de navegador descrita antes no tiene en cuenta las versiones, por lo que tampoco es una alternativa a futuro. Así, aunque sepamos que funciona hoy con una versión de un navegador, la versión siguiente podría no requerir –o lo que es peor, puede que deje de dar soporte a – cualquier solución alternativa que hayamos podido utilizar para que nuestro sitio web funcione bien en ese navegador.

Si tienes que utilizar la detección de navegador, asegúrate de que solo la empleas para detectar navegadores antiguos, tal y como se muestra en este ejemplo, y ten siempre presente la versión de navegador que estás comprobando.

<script type="text/javascript">
    function getInternetExplorerVersion()
    // Returns the version of Internet Explorer or a -1 for other browsers.
    {
        var rv = -1;
        if (navigator.appName == 'Microsoft Internet Explorer')
        {
            var ua = navigator.userAgent;
            var re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})");
            if (re.exec(ua) != null)
            rv = parseFloat( RegExp.$1 );
        }
        return rv;
    }

    function onLoad()
    {
        var version = GetInternetExplorerVersion()
        if (version <= 7 && version > -1)
        {
            // Code to run in Internet Explorer 7 or less.
        }
    }
</script>

En MSDN hay un artículo excelente, con más información si te interesa: "Detecting Browsers More Effectively".

En JavaScript Tutorials puedes leer un artículo muy completo que explica cómo se utiliza el objeto navigator y las expresiones regulares que se necesitan para detectar distintos tipos de navegador y conocer sus versiones exactas.

Otra forma de detector los navegadores es mediante el uso de comentarios condicionales de Internet Explorer. Esta sintaxis extiende los comentarios estándar HTML, y es exclusive de Internet Explorer a partir de la versión 5.

Una manera en que podeos utilizar los comentarios condicionales es con las hojas de estilo CSS. Puedes tener ciertas reglas CSS específicas de IE –reglas que quieres que sean ignoradas en los demás navegadores. En el ejemplo siguiente, un "ie7.css" se carga solo si se detecta Internet Explorer 7 o anterior.

<!--[if lte IE 7]>
    <style TYPE="text/css">
         @import url(ie7.css);
    </style>
<![endif]-->

Puedes encontrar información más detallada sobre todas las posibilidades de los comentarios condicionales en el artículo de MSDN llamado "About Conditional Comments".

Debido a todos los problemas y limitaciones que supone la detección del navegador, vamos a ver ahora otra alternativa.

Detección de funcionalidades

Sin duda la mejor estrategia para manejar las diferencias entre navegadores web para nuestras páginas es la detección de funcionalidades.

Antes de utilizar una funcionalidad que sabes que se implementa de forma diferente en algunos navegadores, puedes lanzar un pequeño test para conocer la disponibilidad en cada caso de un objeto, método, propiedad o comportamiento concreto.

En la mayoría de las ocasiones esto se puede hacer intentando crear una nueva instancia de la funcionalidad en cuestión. Si esta instanciación devuelve un valor distinto de null, el navegador que la ejecuta entiende dicha funcionalidad. Si no, puedes probar a ver si hay alguna otra opción o puedes utilizar alguna implementación antigua equivalente a la funcionalidad no compatible.

Comparación entre detección de navegador y de funcionalidad

Estos diagramas pueden ayudar a clarificar las diferencias entre las dos alternativas en distintas situaciones.

Estas son todas las posibles opciones de código que valdrían para nuestro sitio de pruebas.

Configuraciones de navegador bien conocidas

Cuando nos vemos ante configuraciones de navegador bien conocidas, ambos métodos funcionan bien. Pero la detección de navegador supone de manera directa que tanto la Función A como la B están soportadas en el navegador, mientras que la detección de funcionalidad comprueba cada una por separado.

Configuración desconocida del navegador

La situación se vuelve realmente interesante cuando tenemos que enfrentarnos con configuraciones de navegador desconocidas.

La detección de funcionalidad gestiona este caso bien y puede reconocer que este navegador es capaz de mostrar la Función A pero necesita un código de fallback para la Función B. La detección de navegador elige un camino determinado porque solo comprueba el nombre del navegador, o simplemente opta por la alternativa por defecto cuando no coincide con ninguna de las combinaciones de navegador/versión comprobadas.

De cualquier modo, en este ejemplo la página no se verá bien si empleamos la detección de navegador porque no hay una alternativa que conecte todos los segmentos válidos de código, aun en el caso de que la página incluyera todo el código necesario para verse correctamente en este navegador de configuración desconocida.

Ejemplos de detección de funcionalidad

Hay dos recomendaciones importantes a tener en cuenta a la hora de detector funcionalidades.

La primera, comprobar siempre con estándares, ya que a menudo un navegador soporta un estándar reciente y alguna alternativa para código anterior.

Segunda, en cada comprobación validar solamente las funcionalidades relevantes que se buscan, tratando siempre de suponer el menor número posible de funcionalidades compatibles con cada navegador, para evitar sorpresas.

Vamos a ver algunos ejemplos de detección de funcionalidades.

El siguiente script abre dos alternativas. Con la primera comprueba si el navegador soporta el objeto window.addEventListener, y si no es así, prueba la disponibilidad de la funcionalidad antigua, window.attachEvent.

<script type="text/javascript">
    if(window.addEventListener) {
        // Browser supports "addEventListener"
        window.addEventListener("load", myFunction, false);
    } else if(window.attachEvent) {
        // Browser supports "attachEvent"
        window.attachEvent("onload", myFunction);
    }
</script>

Otra buena táctica consiste en encapsular la detección de funcionalidad dentro de una serie de funciones que puedan utilizarse después a lo largo del código. Aquí mostramos una buena práctica para detectar si el navegador soporta el <canvas> de HTML5 y en este caso, se asegura de que el método canvas.getContext('2d') también funciona. Simplemente devuelve "true" o "false", facilitando la reutilización de la rutina.

<script type="text/javascript">
    function isCanvasSupported()
    {
         var elem = document.createElement('canvas');
          return !!(elem.getContext && elem.getContext('2d');
     }
</script>

Cuando utilices la detección de funcionalidades, utilízala siempre sobre elementos u objetos recién creados. Con eso puedes controlar el caso de que otro script en la misma página haya podido modificar la situación desde el momento en que se creó, lo que dará como resultado un comportamiento errático e imprevisible.

La detección de funcionalidad funciona también directamente sobre una serie de elementos HTML elegidos, como los <video>, <audio> y >canvas> de HTML5 en forma de "fallback". El navegador muestra el primer sub-elemento soportado a partir del elemento superior y oculta visualmente los elementos que quedan por debajo.

En su forma más sencilla, el procedimiento quedaría así:

<video src="video.mp4">
     Su navegador no soporta videos de forma nativa.
</video>

Un navegador que soporta el elemento <video> mostrará el vídeo llamado "video.mp4" y un navegador no compatible, caerá en el fallback mostrando el texto. Pero el fallback también funciona para distintos formatos de vídeo en la misma etiqueta:

<video>
     <source src="video.mp4" type="video/mp4" />
     <source src="video.webm" type="video/webm" />
     Su navegador no soporta videos de forma nativa.
</video>

En este caso, el navegador que soporta la etiqueta <video> de HTML5 empezará a cargar el vídeo MP4. Si no soporta este formato, cae en fallback al vídeo codificado como WebM. Y si este formato tampoco estuviera soportado, o si el navegador es incompatible con la etiqueta <vídeo>, lo que aparece en pantalla será el texto de aviso.

Por supuesto, en vez de mostrar esta frase, puede que sea mejor idea desviar el fallback hacia un reproductor de vídeo basado en un plug-in si el navegador no soporta el <video> de HTML5. En el siguiente ejemplo utilizamos un reproductor de vídeo Silverlight para eso::

<video>
    <source src="video.mp4" type='video/mp4' />
    <source src="video.webm" type='video/webm' />
    <object type="application/x-silverlight-2">
        <param name="source" value="http://url/player.xap">
         <param name="initParams" value="m=http://url/video.mp4">
    </object>
     Download the video <a href="video.mp4">here</a>.
</video>

Una lógica muy parecida se aplica también a CSS. En CSS, toda propiedad no reconocida simplemente se ignora. Así que cuando quieras añadir múltiples propiedades, de tipo experimental o específicas de un navegador concreto, como ocurre con "border-radius" que se muestra aquí, simplemente bastaría con incluir todas las variaciones dentro del código. Puede parecer algo impreciso, pero es sencillo y nos resuelve la papeleta en este tipo de situaciones.

<style type="text/css">
     .target
     {
         border-radius: 20px;
         -moz-border-radius: 20px;
         -webkit-border-radius: 20px;
     }
</style>

Una de las bondades de la detección de funcionalidad es que funciona con navegadores que no habíamos tenido en cuenta a la hora de crear la página. Y como resulta que no parte de suposiciones sobre ningún navegador, también debe funcionar con versiones futuras de éstos.

Desarrollo y prueba de la detección de funcionalidad

Si ya tienes claro que quieres desarrollar y probar la detección de funcionalidad en muchos navegadores distintos, deberás probar las Herramientas de desarrollo F12 de Internet Explorer 9. Puedes utilizarlas para depurar el script paso a paso, cambiar las cadenas de agente de usuario del navegador e indicarle a Internet Explorer que utilice el motor de restitución de versiones anteriores.


Uso de puntos de interrupción para depurar JavaScript en Internet Explorer 9.


Ejecución en "Modo de documento: estándares IE9" el navegador utiliza el método moderno "addEventListener".


Ejecutado en "Modo de documento: estándares IE7", el depurador hace un fallback hacia el método antiguo "attachEvent" .

Se puede cambiar la cadena de agente de usuario en Internet Explorer sobre la marcha desde las herramientas de desarrollo, pudiendo incluso añadir las nuestras propias, por ejemplo para probar identificadores de navegador de dispositivos móviles.

Manejo de la detección de funcionalidad en grandes proyectos

A la hora de crear proyectos web complejos, la creación y manipulación de todo el código de detección de funcionalidades por uno mismo puede llegar a ser bastante tedioso.

Por suerte existen unas librerías excelentes de Javascript para ayudarnos en esta labor, como son Modernizr y jQuery.

Modernizr ya dispone de detección integrada para la mayoría de funcionalidades de HTML5 y CSS3, que podemos utilizar desde nuestro propio código de manera sencilla. Modernizr es una librería muy conocida y mejorada constantemente. Tanto Modernizr como jQuery se incluyen dentro del as herramientas MVC de ASP.NET.

Echa un vistazo al código siguiente que comprueba si el navegador puede mostrar ciertas fuentes web:

Sin Modernizr Con Modernizr
function(){
    var
     sheet, bool,
     head = docHead || docElement,
     style = document.createElement("style"),
     impl = document.implementation || { hasFeature: function()
     { return false; } };

     style.type = 'text/css';
     head.insertBefore(style, head.firstChild);
     sheet = style.sheet || style.styleSheet;
     var supportAtRule = impl.hasFeature('CSS2', '') ?
         function(rule) {
             if (!(sheet && rule)) return false;
                 var result = false;
             try {
             sheet.insertRule(rule, 0);
             result = (/src/i).test(sheet.cssRules[0].cssText);
             sheet.deleteRule(sheet.cssRules.length - 1);
        } catch(e) { }
         return result;
         } :
         function(rule) {
             if (!(sheet && rule)) return false;
             sheet.cssText = rule;
            
             return sheet.cssText.length !== 0 &&
         (/src/i).test(sheet.cssText) &&
         sheet.cssText
            .replace(/r+|n+/g, '')
             .indexOf(rule.split(' ')[0]) === 0;
         };
     bool = supportAtRule('@font-face { font-family: "font";
     src: url(data:,); }');
     head.removeChild(style);
     return bool;
};
<script type="text/javascript" src"modernizr.custom.89997.js"></script>

<script type="text/javascript">
if (Modernizr.fontface){
// font-face is supported
}
</script>

"¡Ayuda!" Qué hacer cuando el navegador no soporta la funcionalidad que necesitamos

Si al comprobar una determinada funcionalidad vemos que el navegador no la soporta, siento decirlo, pero la detección de funcionalidad no nos va a salvar de tener que diseñar una alternativa.

En el ejemplo de vídeo de HTML5 que veíamos antes, el uso de Silverlight como caso de fallback era una solución evidente. Pero ¿qué podemos hacer con otras funcionalidades de HTML5 como <canvas> o las nuevas etiquetas de semántica de HTML5 como <nav>, <section> y <article>, <aside> o los nuevos <header> y <footer>?

Cada vez hay más "fallbacks precocinados" para la mayoría de las funcionalidades de HTML5, llamados "shims" y "polyfills". Los shims y polyfills son librerías de CSS y Javascript (o algunas veces controles de Flash o Silverlight) que puedes añadir a tu proyecto, que te sirven para suplir las funcionalidades de HTML5 no soportadas que necesitas.

La idea general es que los desarrolladores deben poder desarrollar con las APIs de HMTL5 y los scripts pueden crear los métodos y objetos que se necesitan. Si desarrollas tus sitios web bajo este criterio, pensado para el futuro, consigues que aunque los usuarios actualicen su navegador, tu código siga funcionando sin cambios y los usuarios pueden migrar sin problemas hacia una experiencia más actual y de mejor calidad.

La diferencia entre shims y polyfills es que los shims solo "imitan" la funcionalidad, pero con su API propietaria. Los Polyfills emulan tanto la funcionalidad como el API exacta de la funcionalidad HTML5 a la cual sustituyen. Así, en términos generales, el uso de polyfills nos ahorra la necesidad de tener que insertar en el código un API propietaria..

La colección HTML5 Cross Browser Polyfills del sitio Github contiene una lista cada vez más extensa de shims y polyfills disponibles.

Modernizr, por ejemplo, incluye el "HTML5Shim" para dar soporte a etiquetas de semántica, pero podemos cargar fácilmente otros shims/polyfills si Modernizr detecta una funcionalidad no soportada.

¿Si cargo todas estas librerías de scripting no va a hacer que mi página crezca enormemente y tarde mucho en cargarse?

¡Me allegro de que me lo preguntes! Es cierto que si añadimos todas estas librerías complementarias vamos a añadirle bastante código a nuestro sitio, y aumentará su complejidad también. Pero es por eso que debemos acudir a esta solución con prudencia, cargando dinámicamente las librerías cuando sea preciso. Con un shim o un polyfill la buena práctica consiste en cargarlas solamente cuando hemos detectado que el navegador las necesita de verdad porque la funcionalidad nativa de HMTL5 no está soportada.

Pero de nuevo, ¡estás de enhorabuena! Existe una excelente librería que soporta precisamente este escenario: yepnope.js

Se trata de un "cargador de recursos" asíncrono que funciona tanto con Javascript como CSS e independiza totalmente la precarga de la ejecución. Esto quiere decir que dispones de control pleno sobre el momento de ejecución del recurso y puedes cambiar el orden sobre la marcha. Yepnope se integrará dentro de Modernizr 2, pero se puede utilizar también aparte.

Veamos cómo es la sintaxis de yepnope:

<script type="text/javascript" src="yepnope.1.0.2-min.js"></script>

<script type="text/javascript">
     yepnope({
          test : Modernizr.geolocation,
          yep : 'normal.js',
          nope : ['polyfill.js', 'wrapper.js']
     });
</script>
En este ejemplo se comprueba si el navegador puede utilizar la función de geolocalización de HTML5 utilizando Modernizr. Si está soportada, se carga su propio código (normal.js) y si no, carga un polyfill específico (que consiste en la combinación de polyfill.js y wrapper.js).

En resumen: lo que hay que hacer y lo que no

Vamos a terminar resumiendo los puntos más importantes

Puedes encontrar más información sobre la detección de navegadores y funcionalidades aquí:

Sascha P. Corti

Desarrollador Evangelista en Microsoft Suiza

Manual