Overflow en el móvil Android 2.x

Android 2.x no aplica overflow

La adaptación de una web a los dispositivos móviles es una tarea compleja. Hay muchos aspectos que tener en cuenta, especialmente los que tienen que ver con la forma en que los diferentes navegadores se comportan. En este caso se trata de la propiedad de estilo overflow y su tratamiento por el navegador incluido en Android 2.x. Probemos con un contenedor como el siguiente, con texto que sobrepasa tanto el alto del contenedor que se fija en 10em (unos 160px) como el ancho cuando la ventana del navegador es menor de unos 920px:

Ejemplo:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vitae ipsum sem. Curabitur euismod nisl justo, sit amet suscipit nibh porttitor id. Vestibulum nec nibh at tellus vehicula malesuada iaculis non leo. Cras dapibus molestie magna gravida pretium. Quisque sed odio augue. Nullam tempus erat vel purus vulputate, condimentum dignissim velit blandit. Sed nec venenatis turpis. Nullam euismod eros eu odio dictum vestibulum. Aliquam erat volutpat. Mauris aliquet malesuada erat, sodales auctor dolor sollicitudin eget. Morbi varius, lacus eu pretium tincidunt, magna ante aliquam ipsum, eget ullamcorper mauris massa at elit. Aenean sed odio nec tellus consectetur accumsan. Pellentesque ullamcorper auctor commodo. Vivamus vitae mauris iaculis, auctor sem sit amet, auctor magna. Suspendisse tincidunt vulputate mi. Nunc vehicula nibh sit amet porta tempus. Sed non sem a dui mattis condimentum. Quisque at convallis mauris, vel luctus ligula. Sed aliquet ligula risus, nec vehicula eros sodales a. Sed consectetur, risus non eleifend fringilla, quam eros vestibulum lacus, non rutrum quam est nec quam. Integer molestie at neque et vehicula. Nam consequat ultricies nulla a pellentesque. Phasellus non viverra libero. Vestibulum eros orci, volutpat a dui vitae, consequat consectetur diam. Integer vehicula dictum nisl, sed consectetur sapien adipiscing eget. Nullam vel fermentum quam. Praesent sodales urna at metus fringilla, at varius lacus semper. Mauris ut tellus vitae massa semper suscipit. Vivamus velit ipsum, ornare sed lobortis at, venenatis vitae neque. Aliquam a mauris nec orci ultricies vehicula. Aliquam in semper augue, nec facilisis orci. Donec ac rutrum quam, nec feugiat nibh. Quisque varius massa eu sapien auctor, id vehicula augue pharetra. Mauris ultrices nisi non mauris convallis sodales. Quisque risus lectus, condimentum sed consequat vitae, laoreet sed purus. Cras cursus, odio condimentum lacinia consectetur, magna mauris facilisis mi, sit amet porttitor dui diam vel purus. Donec sit amet turpis quis nisl molestie egestas. Vivamus vel bibendum elit, nec molestie dolor. Nam vel pretium turpis, eget scelerisque sapien. Fusce sed est sed elit porta aliquam at non neque. Aenean eros urna, mattis vel pulvinar quis, faucibus sit amet metus. Cras erat lorem, congue vel sagittis dictum, ultrices sit amet tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Ese contenedor tiene estilo en línea height: 10em y white-space: pre para que el texto ocupe un área mayor que la del contenedor con un ancho de ventana menor de unos 920px. Además también tiene el estilo overflow: auto para que el navegador presente un mecanismo que permita acceder al contenido que sobresale. En cualquier navegador de sobremesa podemos acceder usando las barras de desplazamiento o scroll. Incluso en navegadores móviles donde podemos desplazarnos deslizando el contenido, a excepción de Android 2.x que da la impresión de ignorar overflow. Con JavaScript podemos extraer el valor actual de esta propiedad y de paso el navegador que estamos usando:

Ejemplo:

Estilo overflow:
Navegador:

Probando en un Samsung con Android 2.3.6 veo que el estilo actual de overflow es auto tal como se especificó. Pero no aplica ningún mecanismo para acceder al contenido que sobresale. Ni siquiera con valor overflow: scroll. Cualquier cosa que pongamos viene a ser como si pusiéramos overflow: hidden. Esto, que al parecer no sucede en versiones superiores a la 2, es realmente desesperante. Podríamos arreglarlo si le ponemos medidas de ancho y alto con valor auto a los contenedores afectados. Pero es una lástima que sólo por unas versiones de un navegador tengamos que condicionar el resto que si soportan overflow.

El uso de Android 2.x justifica arreglar overflow

Las versiones de Android 2.3.3 a 2.3.7 tienen un 33% de distribución del total de dispositivos corriendo bajo sistemas Android, con datos recopilados durante un período de 14 días finalizando el 1 de agosto de 2013. Una reproducción de la tabla completa que se encuentra en Developer Android se expone a continuación:

VersionCodenameDistribution
1.6Donut0.1%
2.1Eclair1.2%
2.2Froyo2.5%
2.3 -
2.3.2
Gingerbread0.1%
2.3.3 -
2.3.7
33.0%
3.2Honeycomb0.1%
4.0.3 -
4.0.4
Ice Cream Sandwich22.5%
4.1.xJelly Bean34.0%
4.2.x6.5%

El porcentaje de uso de las versiones 2.x es alto (casi un 37%) y creo que merece aplicar algún parche para ofrecer al usuario un mecanismo de scroll. En las páginas overflow-scrolling de Kyle Barrow o scroll-div de Jeff Baker puede ver detalles de soporte y soluciones propuestas para arreglar lo de Android 2.x. Por último iScroll es un componente que también podría resolver ese problema y de paso obtener otras prestaciones.

Por mi parte intentaré buscar mi propia solución y de paso aprender algunas cosas. No es fácil filtrar por la característica para determinar si un navegador tiene o no el mecanismo de scroll activado, pues realmente Android 2.x soporta overflow: auto pero no funciona ningún tipo de scroll. Entonces habría que filtrar por el navegador y versión, aspecto que no se recomienda, pero que es la única forma que veo para aplicar con JavaScript algo que haga las veces de scroll. El siguiente cuadro contiene texto de ejemplo como el del apartado anterior. Pero ahora en un Android 2 (al menos en la versión 2.3.6 donde he podido probarlo) veremos unos botones que nos permitirán acceder al contenido que sobresale:

Ejemplo:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vitae ipsum sem. Curabitur euismod nisl justo, sit amet suscipit nibh porttitor id. Vestibulum nec nibh at tellus vehicula malesuada iaculis non leo. Cras dapibus molestie magna gravida pretium. Quisque sed odio augue. Nullam tempus erat vel purus vulputate, condimentum dignissim velit blandit. Sed nec venenatis turpis. Nullam euismod eros eu odio dictum vestibulum. Aliquam erat volutpat. Mauris aliquet malesuada erat, sodales auctor dolor sollicitudin eget. Morbi varius, lacus eu pretium tincidunt, magna ante aliquam ipsum, eget ullamcorper mauris massa at elit. Aenean sed odio nec tellus consectetur accumsan. Pellentesque ullamcorper auctor commodo. Vivamus vitae mauris iaculis, auctor sem sit amet, auctor magna. Suspendisse tincidunt vulputate mi. Nunc vehicula nibh sit amet porta tempus. Sed non sem a dui mattis condimentum. Quisque at convallis mauris, vel luctus ligula. Sed aliquet ligula risus, nec vehicula eros sodales a. Sed consectetur, risus non eleifend fringilla, quam eros vestibulum lacus, non rutrum quam est nec quam. Integer molestie at neque et vehicula. Nam consequat ultricies nulla a pellentesque. Phasellus non viverra libero. Vestibulum eros orci, volutpat a dui vitae, consequat consectetur diam. Integer vehicula dictum nisl, sed consectetur sapien adipiscing eget. Nullam vel fermentum quam. Praesent sodales urna at metus fringilla, at varius lacus semper. Mauris ut tellus vitae massa semper suscipit. Vivamus velit ipsum, ornare sed lobortis at, venenatis vitae neque. Aliquam a mauris nec orci ultricies vehicula. Aliquam in semper augue, nec facilisis orci. Donec ac rutrum quam, nec feugiat nibh. Quisque varius massa eu sapien auctor, id vehicula augue pharetra. Mauris ultrices nisi non mauris convallis sodales. Quisque risus lectus, condimentum sed consequat vitae, laoreet sed purus. Cras cursus, odio condimentum lacinia consectetur, magna mauris facilisis mi, sit amet porttitor dui diam vel purus. Donec sit amet turpis quis nisl molestie egestas. Vivamus vel bibendum elit, nec molestie dolor. Nam vel pretium turpis, eget scelerisque sapien. Fusce sed est sed elit porta aliquam at non neque. Aenean eros urna, mattis vel pulvinar quis, faucibus sit amet metus. Cras erat lorem, congue vel sagittis dictum, ultrices sit amet tellus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

overflow para móviles android 2

La imagen anterior es una captura de pantalla del navegador de un móvil Samsung con Android 2.3.6 presentando la solución que he desarrollado. Se observan cuatro botones en la parte inferior del contenedor anterior. Este "parche" quizás no es muy elegante. El aspecto de los botones podría mejorarse.

En Diciembre 2013 he cambiado esos botones por elementos <button> además de otro cambio importante (ver una nota de aclaración más abajo)

Pero en cualquier caso al menos el usuario puede acceder al contenido que sobresale. Y de paso este ejercicio me ha servido para empezar a entender algo sobre los eventos de toque (touch events) y el uso de emuladores de navegadores móviles para probar las páginas como veremos a continuación.

Probando las páginas en emuladores de navegadores móviles

Hasta ahora el desarrollo de páginas web lo vengo haciendo mediante un servidor Apache con PHP montado en modo localhost. Voy comprobando los resultados en varios navegadores de ordenadores de sobremesa que tengo instalados (Chrome, Firefox, Opera, Safari e Internet Explorer). Especialmente uso Chrome y Firefox debido a sus herramientas de desarrollo que me resuelven un montón de problemas.

Para probar cosas como las consultas de medios podemos redimensionar la ventana del navegador pues se aplicará de nuevo el estilo CSS. También es posible usar la herramienta de desarrollo de Chrome para sobrescribir las configuraciones del navegador. Se encuentra entre las configuraciones de la herramienta de desarrollo, menú Overrides. Podemos seleccionar un tipo de navegador (user-agent), medidas del dispositivo, emular tipo de medio CSS, eventos de toque (touch events), geolocalización y orientación del dispositivo. La siguiente imagen es una captura de pantalla de la configuración donde he seleccionado un Android 2.3 como tipo de navegador con medidas del viewport de 320×480, las mismas que el móvil anterior Samsung con Android 2.3.6.

Emulando móvil con Chrome

Al actualizar esta página se observa que el navegador crea una ventana de 320×480 emulando ese dispositivo móvil. La siguiente imagen es una captura de pantalla del Chrome en ese momento y a la altura del contenedor de prueba. Aparecen en la parte baja del contenedor los botones de scroll que he desarrollado para acceder al contenido. Aunque como estamos en un navegador de sobremesa también aparecen las barras típicas de scroll.

Developer Tools Chrome, overrides

Por último podemos usar herramientas de visualización como las del sitio manymo.com. Ahí podemos elegir entre distintos tamaños de dispositivos, emulando todos Android de varias versiones. Seleccionando uno de 320×480 con Android 2.3 y lanzando esta página obtengo esta captura de pantalla, apareciendo también los botones de scroll:

Emulador manymo.com

Una limitación en el desarrollo web en móviles es que si queremos probar una página en un dispositivo móvil real tendremos que subirla a nuestro sitio en producción. Con un servidor en localhost podemos acceder en el mismo ordenador donde estamos desarrollando la página haciendo uso de la IP 127.0.0.1. Realmente todo el grupo de direcciones que empiezan por 127 se reservan precisamente para este cometido. Pero desde un móvil no podemos acceder a ese grupo de IP, ni creo que tampoco desde el emulador de manymo.com. Quizás otros emuladores si lo permitan. Es posible que existen técnicas para conseguir conectar un móvil con un servidor web montado en local, pero esto es una tarea pendiente para otra ocasión.

Scroll móvil para Android 2.x

En este apartado mostraré en líneas generales el funcionamiento de esos botones de scroll. Puede ver una página de ejemplo con el JavaScript necesario para implementarlos. El código del JavaScript puede consultarse en esa página de ejemplo, pero también lo expongo a continuación:

En el window.onload de esa página de ejemplo encontramos lo siguiente:

    
cargarScrollMovil([["id", "overflow-movil", "hv"]]);
    

Esta instrucción carga el scroll móvil en el elemento con identificador overflow-movil, que es el contenedor con texto de ejemplo. El argumento es un array para cargar el scroll sobre una colección de elementos. Cada uno de ellos se determina en otro array con las siguientes posiciones:

  • Un string con "id" o "cls" que indica si se carga sobre un elemento con un id o sobre todos los elementos de una clase (class).
  • Un string para el identificador o nombre de clase.
  • Un string opcional "h", "v" o "hv" que indica si se aportan botones de scroll horizontal, vertical o ambos. Si se usan mayúsculas el botón correspondiente será presentado en todo caso. Con minúsculas se presenta sólo si el contenido sobresale. El valor por defecto es "v".
  • Un string opcional como una cadena de estilo CSS para agregar estilo adicional a los botones (en el ejemplo anterior no se ha utilizado). El valor por defecto es una cadena vacía.

La variable oScroll.debug con valor true nos permite visualizar el funcionamiento en el emulador del navegador Chrome de sobremesa, puesto que usamos la instrucción createEvent("TouchEvent") para detectar si estamos en un navegador que soporta eventos de toque y en otro caso no hacemos nada puesto que entonces soportará las barras de desplazamiento típicas. Para una situación de producción normal no sería necesario modificar esa variable. La función cargarScrollMovil() se encarga de detectar el navegador Android 2.x montando el HTML de los botones.

Dev-Tools Chrome: Overrides emulate touch events Diciembre 2013: Estaba usando navigator.userAgent para detectar el navegador de Android 2.x, pues no he encontrado una forma de detectar esa incidencia sin utilizar userAgent. A partir de esta fecha he decidido no utilizar más la detección de navegador en ningún script de este sitio, por lo que incorporaré este scroll móvil a todos los navegadores que soporten eventos de toque (Touch Events). En cualquier caso esta funcionalidad sólo la aplicaré a los contenedores de código resaltado principalmente.

En esta página verá los botones de scroll también en los códigos resaltados cuyo contenido supere el ancho y/o alto del contenedor, incluso en ordenadores que no soporten eventos de toque, puesto que en esta página he declarado la variable oScroll.debug = tue. Pero los botones de scroll sólo funcionarán si se emulan los eventos de toque con algo como el Developer Tools de Chrome, en la parte de overrides emulate touch events.


Enero 2014: Hasta ahora detectaba si un navegador soportaba eventos de toque ejecutando document.create("TouchEvent"). Con las versiones de Chrome 31 y anteriores así como el resto de navegadores, esta instrucción ocasiona un error que se detecta con un bloque try-catch, lo que nos permite hacer el test de soporte. Pero la nueva versión 32 permite crear un evento de toque incluso aunque el dispositivo no lo soporte. Esto lo hace Chrome para irse adaptando a dispositivos que permitan al mismo tiempo eventos de toque y de ratón. Por lo tanto si está viendo esta página con Chrome 32+ en un ordenador que no soporta eventos de toque verá también que aparecerán los botones de desplazamiento.

Esto es un contratiempo pues la idea es que no aparezcan estos botones si el dispositivo usa un apuntador de ratón. Pero como dije antes, ya hay dispositivos con posibilidad de usar ratón, toque y otros métodos de punteros, todos al mismo tiempo.

Cada botón tiene dos eventos: touchstart y touchend. Estos junto a touchmove son los eventos de toque. Es equivalente a mousedown, mouseup y mousemove para los eventos de ratón. Pero un navegador móvil no tiene ratón y sólo podemos actuar con esos eventos de toque. Tiene también un evento click que, de forma similar a los de ratón, se ejecuta en el orden touchstart, touchend y click.

Queremos que al pulsar en un botón y mientras se mantenga presionado se ejecute el scroll. Esto será con el encendido de touchstart. Cuando soltemos el botón se enciende touchend y el movimiento del scroll parará. La función iniciarScrollMovil() se encarga de empezar a mover el scroll. Usamos event.preventDefault() para impedir otros comportamientos del navegador, como el zoom por ejemplo. Obtenemos la altura del contenedor a partir de su estilo actual o computado (getComputedStyle()), calculando un salto de una quinta parte de esa altura y limitado a 48px que son unas tres líneas de texto.

Modificamos el scroll vertical sumando o restando de scrollTop y el horizontal usando scrollLeft. Para que funcione de forma reiterada mientras se mantiene presionado el botón usamos setInterval() que lanzará esa tarea cada 100 milisegundos. Este valor es el que considero adecuado tras probarlo en un Android 2.3.6. Valores inferiores harán que el scroll avance demasiado rápido. Además el valor debe estar por debajo de 300ms, puesto que es posible que el navegador ejecute algúna tarea reconociendo una pulsación larga sobre la pantalla (como mostrar el menú de copiar texto por ejemplo). La función pararScrollMovil() detiene la ejecución reiterada de la función declarada en setInterval(), lo que sucederá con el evento touchend.