> Scrolling div on iPhone/iPod Touch

Recently I had to develop a web application for the iPhone and I was impressed by the possibilities offered by the little device. You can easily render complex 3d animations with a bunch of CSS rules or save data on a sqlite database, but I was surprised to discover that there is no easy way to scroll the content inside a fixed height div.

Update 2009/11/30: Development of iScroll v3.0 has been started. Please head to the new version. This post will be soon obsolete.

Update 2009/01/07: Thanks to a mighty commenter the scrolling function has been updated.

Update 2009/01/18: Look Ma, without timers! The script has been updated once again. I got rid of the timer I used to reset the touchMove action, now we are timers-free. Additionally all the nasty anonymous function calls have been removed and replaced by the chic code suggested by Antoine Quint.

Update 2009/02/03: Kudos to Andreas Ritter that managed to optimize the deceleration formula. Now the script mimics very closely the native iPhone scrolling. Other small enhancements have been done, so be sure to download the latest version of the script at the bottom of this post.

Update 2009/02/12: autoScroll renamed to the fancier scrollTo. By popular demand I added the refresh() method that should be called each time the scroller content is programmatically modified (by an ajax call for instance). Code released under the MIT license (so you can pretty much do anything you want with it).

Update 2009/06/18: the script has been finally updated for OS3.0 compatibility.

Update 2009/07/09: it turned out that Apple is well aware of the webkit refresh bug, fortunately they are kind enough to offer a workaround. All we have to do is to use translate3d instead of translate and all our animations will be fluid as never before! Go get the latest version of the script!

Apple disabled all the scrolling functionalities on the webkit for iPhone, javascript functions and events included. So basically you can scroll the whole screen with one finger but to scroll elements inside the page (e.g. iframes) you have to use a two-fingers gesture.

While this is perfectly logical on a standard web page, can be limiting on a web application. Let’s say you want a fixed position header or footer and a scrolling center area, well… you can’t. (Did I say that position:fixed does not work?). Many iPhone behaviors are triggered with meta tags, or CSS directives or javascript but as far as I can tell there is no easy way to scroll content inside a fixed size div.

Fortunately we can take advantage of the powerful javascript engine and some hardware accelerated animations. The good news are that the script is compact (4kb uncompressed/unminified), self contained and the scrolling extremely smooth. The bad news is that it’s actually a “hack” so it’s not perfect (see below) but usable.

Enough blabbing. Point your iPhone, iPod touch or emulator to my working demo or watch the quick screencast. If you are curious about how the script works read the following chapter, otherwise skip to How to use the script.

How it works

The latest iPhone update (2-point-something) added touchstart, touchmove and touchend events. We hook at those events to capture finger movements and use the -webkit-transform CSS property to translate the div content up and down simulating the scrolling. You can easily expand the script to scroll in any direction, but I leave this task to the diligent reader :P .

e.preventDefault(); prevents the whole page to scroll but it also disables all the useful events we may need. Most notably the mouse click (or finger tap) is not executed so we have to simulate it.

On touchend if no scrolling is due I try to create a new mouse click with the following:

var theTarget = e.target;
if(theTarget.nodeType == 3) theTarget = theTarget.parentNode;
var theEvent = document.createEvent("MouseEvents");
theEvent.initEvent('click', true, true);
theTarget.dispatchEvent(theEvent);

The click is cast indiscriminately, not only over “clickable” objects (such as anchors or buttons). I bet there is a fancier way to do that, none that I could find though.

To simulate the scrolling deceleration I initially used a timer but the script has been recently updated after a reader suggested to use window.getComputedStyle() to get the in-transition position. This brilliant solution removes the timer but needs an additional event listener. I don’t know which solution consumes less resources, but the new version seems more logical as it relies completely on -webkit-transform for animations and transitions.

The core of the new code is:

var theTransform = window.getComputedStyle(this.element).webkitTransform;
theTransform = new WebKitCSSMatrix(theTransform).m42;
    if( theTransform!=this.position )
        this.position = theTransform;

webkitTransform returns a matrix of all the transformations applied to an object. All we need is the Y position which is stored in WebKitCSSMatrix(theTransform).m42. If the user taps the screen while the animation is rolling, the transition stops and content holds its position.

How to use the script

On the HTML side all you need is a fixed size element that wraps the scrolling area.

<div id="wrapper">
    <div id="scroller">
        content
        ...
    </div>
</div>

The wrapper needs the following styles:

#wrapper {
position:relative;
z-index:1;
height:200px;        /* Desired element height */
}

Remember to give the #wrapper a z-index.

The #scroller does not need any style at all. You don’t even need to make it position:absolute as everything is handled by -webkit-transform.

To activate the scrolling add on window load:

new iScroll(document.getElementById('scroller'));

It’s better to let the window rest few milliseconds before running the script:

window.onload = function() { setTimeout(function(){ new iScroll(document.getElementById('scroller')) }, 100) };

If the content of the scroller changes on the fly for any reason (eg: http request), remember to call the refresh() method, it takes care of some variables initialization.

As a bonus feature you can scroll to any position programmatically by calling the function scrollTo(destination, duration) (see the example for details).

Known issues and limitations

The standard pop up that appears when holding on anchors does not come up (does anyone care?).

Text fields inside the scrolling area behave weirdly (but probably it can be fixed).

Remember to add a timeout on window load. In my experience the onload timeout needed by webkit to be really helpful is 100ms minimum. (That is a bit more than I hoped).

Please note that the iPhone can’t handle too many objects in the scrolling area. For best performance remove all unneeded tags from the scroller.

The script is not perfect and maybe not even elegant, but it works pretty well. I hope you can use it as a starting point and save few hours programming. If you have any advice to improve my code, please leave a comment.

Download the script

/Share the joy

/Reactions

    • Author: Mickey Shine
    • Posted on: 2009/11/28
    • At: 02:41

    @Terry, I have the same problem with you

  • Comments closed, discussion continues on iScroll project page http://cubiq.org/iscroll