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:
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:
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:
Version | Codename | Distribution |
---|---|---|
1.6 | Donut | 0.1% |
2.1 | Eclair | 1.2% |
2.2 | Froyo | 2.5% |
2.3 - 2.3.2 | Gingerbread | 0.1% |
2.3.3 - 2.3.7 | 33.0% | |
3.2 | Honeycomb | 0.1% |
4.0.3 - 4.0.4 | Ice Cream Sandwich | 22.5% |
4.1.x | Jelly Bean | 34.0% |
4.2.x | 6.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:
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.
<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.
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.
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:
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 unid
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.
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
.