Scrolling a div on multitouch devices using JavaScript

With the emergence of touch devices and the slow but steady tendency of replacing the mouse as an input device, this leaves us with fingers. Both the iOS platform and the Android platform sports APIs for dealing with touches from fingers, for native apps that is. But what about web-apps? Enter the JavaScript touch DOM events. In this post I'll discuss how to use the touch events to scroll a container (a div) using a finger, e.g. on an iPhone.

What you'll need
First of all you need a touch device or a simulator like the iPhone simulator to test your code. The code itself can be written in any text editor, however it is recommended to use an editor with a build in debugger or a test environment that supports debugging JavaScript touch events. You might be thinking that Firebug or Drosera is great for debugging, but how exactly would you trigger the touch events in your desktop browser? Well, you can't. This is why I'm using Dashcode from the iOS SDK for my JavaScript coding. More on using Dashcode in a later post.
Note that this code is written for WebKit based browsers, but should work in principle on other touch browsers as well.

The HTML
<div id="holder">
   <div class="content">
       your content here
   </div>
</div>


Notice that the content is inside of a div within the holder div, this is because you can't scroll the div in which the touch event is attached to.

The CSS

#holder{
   width: 300px;
   height: 200px;
   background-color: blue;
   -webkit-box-shadow: 3px 3px 5px #000;
   padding: 10px;
}

#holder div{
    width:100%;
    height:100%;
    color: white;
    overflow: auto;
    -webkit-user-select: none;
}


Not much revolutionary here, but do note that we set the "overflow" property to auto on the content div, not the holder itself. Also, we set the "-webkit-user-select" to "none" on the content div, thereby preventing the user of selecting the content. This is optional.

The JavaScript
There are three main touch events:
  • touchstart - Called when a finger touches the listening container (finger down)
  • touchmove - Called repeatedly when a finger moves (finger down and moving)
  • touchend - Called when a finger leaves the listening container (finger up)
Each of these methods gives us an event with a touches array. Remember, there might be more than one finger since these APIs are made to handle multitouch devices. To get a hold of the vertical position of one finger you would query the touches array for one finger and get it's pageY property, like this: event.touches[0].pageY

From this we can calculate and set the scrollTop property of the content container. But, wait! If you try this the rest of the page will also scroll, not what we wanted. The reason why this happens is because the touch event bubbles all the way up to the window, making the browser think it needs to scroll the window because you have dragged your finger on it. We need to tell the event to stop bubbling, we will handle the event ourselves. We do this by calling: event.preventDefault()

The complete JavaScript code will then look something like this:

var startPos;
function init()
{
    var scrollArea = document.getElementById('holder');
    scrollArea.addEventListener('touchstart', function(event){
        touchstartHandler(event);
    }, false);

    scrollArea.addEventListener('touchmove', function(event){
        touchmoveHandler(event);
    }, false);

    scrollArea.addEventListener('touchend', function(event){
        touchendHandler(event);
    }, false);
}

function touchstartHandler(e)
{
    startPos = e.touches[0].pageY;
}

function touchmoveHandler(e)
{
    var touch = e.touches[0];
    var targetBox = e.currentTarget.getElementsByTagName("div")[0];
    e.preventDefault();

    var fingerMoved = startPos - touch.pageY;
    startPos = touch.pageY;

   targetBox.scrollTop = targetBox.scrollTop + fingerMoved;

}

This is just a simple example, but it's something you'll probably end up using a lot. Speaking of...

When should you override the default window scroll
On an iPhone there is not as much need for this method as you'll probably scroll the entire screen in most cases anyway. However, the iPad is a different story. Take a look at the iPad version of gMail created by Google. They are using this method with success, and they have added inertia scrolling as well. This can be accomplished using the last touch event we did not implement, touchend. Maybe more on this in a later post. Anyway, you should use you own scrolling when it's appropriate to scroll only a part of the app, like a spilt view list or something like that.

5 comments:

Ben said...

I would really like this to work, but it's not for me. I've tried lots of suggestions online and yours seems like the best, but it's just not working for me.

Anonymous said...

Demo?

Jørn Kinderås said...

There's no demo for this, but it's quite easy to set up. Note however that this will be a built in feature of iOS 5, so you won't need to handle this manually anymore.

Anonymous said...

Worked like a charm on NexusOne..thanks bud for helping out..

Anonymous said...

Thank you, I managed to create this same solution except for one small issue. I was saving the touch event object in the DOM as the last touch instead of just the position. IOS doesn't like it when you do that