Creas lo que creas, te hayan dicho lo que te hayan dicho, no hay modo de saber con certeza si la pantalla de un dispositivo es táctil o no desde dentro del navegador. Y no parece que vaya a haber una solución pronto… Dejad que os explique por qué:

Todo es culpa del navegador 🙂

El navegador es lo que llamamos un ‘sandbox’. Tu aplicación sólo puede obtener la información del sistema que el navegador proporcione, ya sea en forma de HTML, CSS, o APIs JavaScript. El navegador nos proporciona dos aproximaciones a “detección de touchscreen” (Media queries, y Touch APIs), y nos informa del user agent del navegador. Aunque ninguna nos garantiza saber al 100% si se trata de una pantalla táctil o no.

Media Queries (con device width)

Los móviles tienen pantallas táctiles, así que… si tengo una pantalla pequeña, seguro que es táctil, verdad?

var hasTouch = window.matchMedia(‘(max-device-width: 320px)’).matches;
Pues… no. No podemos depender de ese device-width. Porque estamos desconsiderando todos los tablets, y ordenadores con pantallas táctiles.

Touch APIs

Si el navegador soporta eventos como touchstart (u otros eventos en la API de Touch Events), seguro que el dispositivo es táctil, no?

var hasTouch = 'ontouchstart' in window;

Pues… tampoco. Porque nadie dice que los dispositivos “no-táctiles” no implementen Touch APIs, o que tengan los event handlers en el DOM. Por cierto: ésta es la aproximación que utiliza Modernizr.touch (y ellos mismos ya han dicho que no, no podéis depender de Modernizr.touch para saber con certeza si una pantalla es táctil o no).

El user agent

El navegador nos informa también del user agent, que es algo que podríamos utilizar para filtrar por tipos de dispositivo. Si es iPhone, iPad, Android, IEMobile, OperaMini, webOS, etc. podemos asumir que es un dispositivo táctil.

var useragentmatch = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);

Y parece buena idea. El problema es que da falsos positivos y falsos negativos. Es habitual que el user agent sea el mismo para un mismo navegador y sistema operativo, tenga pantalla táctil o no. Por ejemplo, en Internet Explorer y Windows 8, el user agent es el mismo (sea la pantalla táctil o no).

Cuál es el problema real

El navegador mismo no sabe con certeza si una pantalla es táctil o no. Pensad que sólo tiene acceso a los dispositivos via las APIs del sistema operativo, que les informan de que dispositivos de presentación están conectados al dispositivo. Y… esas APIs no son perfectas. En Windows8, con Chrome o Firefox, reportan de la presencia de pantalla táctil cuando realmente la pantalla no lo es; BlackBerry OS da por activadas siempre las Touch APIs, sean los dispositivos táctiles o no…

Y por si fuese poco

En cualquier momento de la sesión en el navegador, el usuario puede conectar un monitor secundario, u otro dispositivo (táctil o no). Y el navegador no va a enterarse ni hacer nada para informar de ese cambio. Y además… no seria fácil dirimir qué deberíamos hacer si tenemos dos dispositivos (uno táctil y otro no) conectados.

Eso sí, hay un modo de saberlo casi al 100% (pero no mola…)

Podéis añadir un Event Listener a algún evento táctil, de forma que si ocurre ese evento, es seguro seguro (a no ser que el navegador haya hecho alguna barbaridad) que el dispositivo es táctil.

var hasTouch;
window.addEventListener('touchstart', function setHasTouch () {
    hasTouch = true;
    window.removeEventListener('touchstart', setHasTouch);
}, false);

El tema es que tiene 3 problemas graves:

Requiere una interacción del usuario (un ‘touch’) antes de que podáis saber el resultado
Si no ocurre ninguna interacción, no tenéis ni idea de que dispositivo es.
Este evento no se dará en navegadores que no soporten Touch Events API… y hay un montón.

Pointer media queries

Las especificaciones de Media Queries Level 4 (que todavía no están cerradas, y sólo implementa parcialmente WebKit) parece que ofrecerán una solución más fiable, aunque no es exactamente “detectar si el dispositivo es táctil o no”, sinó “detectar cuál es el tipo de puntero”.

var hasTouch = window.matchMedia('(pointer: coarse)').matches;

Como son media queries, representa que en el momento en que cambie el tipo de puntero a “puntero táctil”, se ejecutará la media query, así que es una solución dinámica: se enterará cuando se conecte cualquier dispositivo táctil. No hay ningún planning sobre cuándo los navegadores implementarán Pointer media queries, así que, hoy por hoy, no nos sirve de mucho…

Nuestra aproximación

Nosotros necesitamos saber si un dispositivo es táctil o no porque en función de ello cargamos una hoja de estilos CSS, u otra. Además, cambiamos el fontSize de toda la aplicación (body, y root), porqué nuestros CSSs no van con px, ni em, ni pt… sinó con REM, y así, cambiando sólo el fontSize base, hacemos que todo sea un poco más grande en nuestra versión “touch” de la aplicación. Antes hemos visto que no podemos depender al 100% del userAgent, ni de las Touch APIs… Así que lo que hacemos es… mirar las dos, y esperar que alguna acierte. Suena cutre, pero ha resuelto bien si es táctil o no en el >90% de los casos.

function isTouchDevice() {
return true == ("ontouchstart" in window || window.DocumentTouch && document instanceof DocumentTouch);
}
var useragentmatch = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (useragentmatch || isTouchDevice()) {
loadStylesFile('touchStyles.css")', "touchStyles");
fontSizeApplied = "22px";
} else {
loadStylesFile('mainStyles.css")', "mainStyles");
fontSizeApplied = "16px";
}

Lo más importante

Sí que nos basamos en una detección automática como primera aproximación, pero permitimos siempre al usuario configurar si desea trabajar en modo “touch” o modo estándar (con una configuración manual en la aplicación).