Cross-platform javascript touch scrolling

Currently CSS position:fixed property is only partially supported on mobile devices. iOS below version 5 doesn’t support it at all and Android is plagued by fragmentation hell. To solve these problems developers created libraries for JavaScript scrolling powered by CSS3 transitions. We recently wrote from scratch cross-platform mobile web site which uses a combination of native scroll and multiple touch scroll libraries depending on device. In this post I’d like to share our experiences..

Only recently in version 5 iOS began to support CSS position:fixed and before that fixed positioned elements behaved like they’re absolute positioned relative to document body. On Android it is officially supported since version 2.2 but actual behavior of scroll greatly depends on device and manufacturer with end result ranging from great user experience on some Androids v2.2 to horrible glitchy animation on latest versions. Thumbs up to HTC which did a good job on improving stock browsers with their HTC Sense framework and because of that fixed positioning mostly works fine.

JavaScript touch scroll libraries offer a way out of this mess by trying to provide stable cross platform scroll emulation. Basically the idea is to attach JavaScript handlers to document’s touch events, detect swipe gestures on scrollable element, and emulate scroll behavior with CSS3 transitions. At first these JavaScript libraries enabled only basic scrolling, but today there are features like pinch zoom, fancy fading scrollbars, pull to refresh, etc.

iOS devices

On iOS there no support for position fixed until version 5 that only recently came out. By far the best library to use is Matteo Spinelli’s iScroll because of both features it offers and overall smoothness that almost resembles native scroll. Current version is 4 which is a complete rewrite of older iScroll 3 and offers much more than just scrolling. It comes in two flavors, full version with all features and support for desktop browsers, and lite version which offers only touch scrolling in mobile Webkit. It’s written in pure JavaScript and doesn’t require any third party library to function (e.g. jQuery). Unlike version 3, iScroll v4 no longer supports automatic refresh when content updates so you’ll have to call refresh method manually. Although performance is very good, you might want to use native scroll in iOS5 which can be detected through browser’s user agent.


if ((navigator.userAgent.match(/iPhone/i)
  || navigator.userAgent.match(/iPod/i)
  || navigator.userAgent.match(/iPad/i))
  && navigator.userAgent.match(/ OS 5_/i)) {
    // do not use iScroll
}

Android

To get usable and consistent scroll experience on Android proved to be a real challenge. Although position fixed is officially supported since version 2.2 this was only partially true and in most cases did not work. We tested a lot of libraries and best one is touchScroll jQuery plugin by Paul Neave. Under the hood it uses the same scrolling logic as iScroll but works much better and refreshes automatically when content updates. Few things to keep in mind is that there’s a bug causing content flicker when elastic bounce is turned off so it’s best to leave it active. Also there’s no way of destroying touchScroll once it’s been activated.

One other bug we noticed is present only on vanilla Androids (e.g. Nexus S) where scroll freezes after returning to page using back button. To fix this rewrite touch event bindings to use addEventListener instead of jQuery bind() method and fix bug in getTouches(e) method:


function getTouches(e) {
  if (e.originalEvent) {
    if (e.originalEvent.touches && e.originalEvent.touches.length) {
      return e.originalEvent.touches;
    } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
      return e.originalEvent.changedTouches;
    }
  } else {
    if (e.touches.length) {
      return e.touches;
    } else if (e.changedTouches.length) {
      return e.changedTouches;
    }
  }
  return null;
}

Like I said before HTCs handle position fixed much better so you might want to use native scroll. This will also prevent problems with input fields being offset that sometimes occur on HTC devices. For detection you can once again use user agent string but it’s not 100% reliable because Android browser allows user to change it.


if (navigator.userAgent.match(/ HTC/i)
  // other Androids that don't have HTC in user agent
  || navigator.userAgent.match(/ Desire_A8181/i)
  || navigator.userAgent.match(/ myTouch4G/i)
  || navigator.userAgent.match(/ ADR6200/i)) {
    // do not use touchScroll
}

Tips for better performance

  • 1. do not use CSS gradients
  • 2. keep content simple and reduce number of html elements
  • 3. if possible avoid JavaScript scroll entirely. From user experience point of view mobile web sites shouldn’t try to emulate mobile application with fixed header and footer