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.
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:
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.
<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.
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.

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.

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.
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.


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.
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> |
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.
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).

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