chris barr

Photo/Dev/Design

UPDATE: this code may no longer be needed! You may want to use the CSS property -webkit-overflow-scrolling: touch instead. More details can be found on this StackOverflow post.

In mobile Safari on the iPhone, iPod Touch, and iPad (as well as the webkit based browser on Android phones) it’s not immediately obvious how to scroll a div that has overflow:auto; set on it. If this were a desktop browser you would see scrollbars and be able to manipulate those or even use your mouse wheel. No such concepts exist on a touch screen device!

To scroll the entire page you just touch it and move your finger. But when you touch the element that would normally scroll, the entire page scrolls instead. This is a little bit broken in my opinion since there’s no visual indicator that you aren’t seeing all the content. However, if you are on a site and you know there’s a scrollable div there is a simple (but not obvious) workaround. Simple use two fingers at the same time and scroll them in the same direction.

This works OK but like I said it’s not obvious, there’s still no indicator that the content is scrollable, and when you use more than one finger you might accidentally trigger some other gesture like scaling the page. I recently ran into this exact issue at work and came up with a pretty solid solution in javascript. I broke this down into two simple functions, but the last one is where the magic happens.

This first function simply attempts to create a new touch event. Only touch screen browsers like mobile Safari have these events, so if it doesn’t throw an error then we are using a touch screen device. Otherwise it’s probably a desktop browser.

Next, this function calls the isTouchDevice() function, and if it succeeds we attach some special touch events to the page. First when a touch event begins (the touchstart event) we get the current scroll position and prevent the default browser behavior, which in this case would be to scroll the page. Next when you move your finger the touchmove event is called. Here we just subtract the position of your finger from the scroll position we saved earlier and again prevent the page from scrolling.

So that’s it, just include these functions on your page and just call it by passing in the ID of the element you want to scroll. Like so: touchScroll("MyElement");. You can see a working demo here: http://chrismbarr.github.com/TouchScroll. I feel like this is a better way of doing things because it’s more intuitive since you’re just using one finger, and it’s potentially more obvious. Even if you don’t immediately know there’s hidden content, you might accidentally touch this while scrolling the page and realize there’s more to see in this div.

Oh, something you should be aware of: the try/catch block in isTouchDevice seems to have no effect.. As in all the real browsers on desktop computers (Firefox and WebKit—Chrome and Safari) don’t fail in that block and allow a TouchEvent to be created. This doesn’t seem to have any adverse effects on usability, it just could mean that this check is entirely unnecessary.

Wow. I developed our site with desktop in mind. Bought the iPhone 4 and most of the site renders correctly. Then I tried to scroll a long list (client list) and couldn’t! I panicked. Finally found your page and it’s amazing that the double-finger parallel scroll works for scrolling the div! Whew! Didn’t want to make some hack or patch. Since the “double flick” seems to work, it’s odd that Apple wouldn’t include that in their basic gesture manual. I’d rather just tell someone the site works fine and they just need to use the “standard” gesture than make some unnecessary patches. As for no visible scrollbars, that’s a problem. Mine is cutting the middle of content so it’s kind of obvious, but I can see a break in white space would cause problems.

I can see, though, why iPhoneOS gets confused with the gestures. I don’t see why they wouldn’t show scrollbars in overflow DIVs though!! What’s the logic there???

Hey thanks for the code example, works like a charm.
@Brad & Tobias Naess
I found that removing the event.preventDefault() part of the code in the touchstart event allows me to click buttons and links normally

Eventhough it worked great, I discovered webkit-tweening on the iphone has slightly higher framerates.
So I already abandonded the idea above, however its a great non-webkit-easing addition.
(Who knows somebody can use it in the future)

Chris, I think I created a more complete touchScroll function by using the help on this page. It allows x scrolling if need be also:

function touchScroll(id){
if(isTouchDevice()){ //if touch events exist…
var el=document.getElementById(id);
var scrollStartPosY=0;
var scrollStartPosX=0;
document.getElementById(id).addEventListener(“touchstart”, function(event) {
scrollStartPosY=this.scrollTop+event.touches[0].pageY;
scrollStartPosX=this.scrollLeft+event.touches[0].pageX;
//event.preventDefault(); // Keep this remarked so you can click on buttons and links in the div
},false);
document.getElementById(id).addEventListener(“touchmove”, function(event) {
// These if statements allow the full page to scroll (not just the div) if they are
// at the top of the div scroll or the bottom of the div scroll
// The -5 and +5 below are in case they are trying to scroll the page sideways
// but their finger moves a few pixels down or up. The event.preventDefault() function
// will not be called in that case so that the whole page can scroll.
if ((this.scrollTop < this.scrollHeight-this.offsetHeight &&
this.scrollTop+event.touches[0].pageY < scrollStartPosY-5) ||
(this.scrollTop != 0 && this.scrollTop+event.touches[0].pageY > scrollStartPosY+5))
event.preventDefault();
if ((this.scrollLeft < this.scrollWidth-this.offsetWidth &&
this.scrollLeft+event.touches[0].pageX < scrollStartPosX-5) ||
(this.scrollLeft != 0 && this.scrollLeft+event.touches[0].pageX > scrollStartPosX+5))
event.preventDefault();
this.scrollTop=scrollStartPosY-event.touches[0].pageY;
this.scrollLeft=scrollStartPosX-event.touches[0].pageX;
},false);
}
}

@meeso:
quickfix: if you removedevent.preventDefault();
from the “touchstart” block, then links do work, a downside being that you need to tap into a scrolling area first to the be able to scroll then – I guess it should be done better

Great code Chris. I have integrated it with jQuery 1.4+ and Modernizr to shorten the code somewhat. The function below uses the jQuery live() function which means it can also be used with elements that are created by Javascript after DOM loading:

Excellent job Chris! (And other guys who left valuable comments/additions!) I ran into the links issue, but noticed that had been covered in the comments as well. Thanks for sharing (and saving me hours of frustration)!!

Given that iOS 5 now supports overflow:scroll, I naively thought I could just use that and be done. Then I tried my page on an Android 2.3 device and discovered the error of my ways. Thank you for this script! It’s certainly made my life easier.

Hi there, I try to help a friend of mine to implement this code to his existing older page without luck. Maybe you can get in touch with me by e-mail to cross check where our problem is.
Thank you in advance.

I’m trying to use this on two scroll panels in the same page, but regardless of what I do with the code I can only get one of the panels to scroll on a mobile device. One panel always remains non functional when it comes to scrolling and this is when both panels need to scroll. Do you have any suggetions for resolving this problem. Thanks in advance.

@craig-c if you use Jquery, grab my version of the code (two comments above yours) and give any elements you want to scroll a class, let’s say “scrollable”. Then call the function like this:touchScroll(”.scrollable”);
As I mentioned before, be sure to call it inside $(document).ready().

Thanks guys. However, after having a second look I realised the problem was with the scrollStartPos variable in the touchScroll function. I’ve therefore changed that so I have a separate version of that variable for each scroll list. I’ve also duplicated the addEventListener calls (one pair of function calls for each scroll list). Anyway, all is now working fine. Thanks again

thanks for the great code. now a user can scroll the content in the div.
but only user with a galaxy s II have to touch the dots on the side of the div box that they can slide the content. many people don’t see the dots.
does someone has the same problem and a solution for this?
thanks

With this system the scrollbar is hidden, giving problems for users to notice there’s more content if they scroll.
However if we add, in the CSS, the ::-webkit-scrollbar… options to cutomize the scrollbars, they magically appear both in Android and iOs (at least with the devices I tried).
If this works not only on my three devices but also on other ones, I think we almost resolved the overflow problem – scrollbars included :)

@Lazza Thanks Man, your code worked amazingly, I should say this is the full proof solution that you have given, this scroll was not working in the android initially as we had a div inside a popup overlay in mobile, and using your snippet it worked.

Just two things I would like to highlight here is
1) Irrespective of where your function is placed in the JS but function call should be made within $(document).ready() itself.
2) Unlike the original chris-barr’s code, in this you need to pass “#yourelementid” or “.yourelementclass” without the relevant prefixes this won’t work.

Clemens, it works, try it here with your device: http://fiddle.jshell.net/df7n8/2/show/
Please note that, as I said, the code I posted in comment 38 suffers the “fancy quotes” effect. You can copy the code from the fiddle here if you prefer: http://fiddle.jshell.net/df7n8/2/
Jatin, I’m glad it worked. Your comment is absolutely right, and I think it’s a good reminder for everyone who wants to use my solution. :) Using a combination of custom selectors (all Jquery selectors work) and the delegate method allows a more versatile solution which works even for elements which are “added in the future”. Thanks to Jeff and Cormac for providing much of the code I put together, I just added the delegation.

I deeply desagree with your vision of no-zoom sites. Zoom is there for some reason (and accesibility can be one of them). I fully dislike mobile specific sites, and i always use the “view as in desktop” check on mobile browsers.

Zoom is there for a specific reason: a lot of sites don’t have a mobile specific version or a responsive layout (which is the best option). So if you are designing your website and your doing it the right way (responsive) then the user will not zoom it, he/she will be able to read it comfortably.
BTW do you get the same problem also with my version of the JS or only with the one in the blog post?

Lazza’s code worked very well, but there is a issue that exists now in devices which have both touch as well as trackpad like blackberry, in that case the trackpad touch does not work, this can be due to the fact that we are preventing the defaults within that but no concrete solution I am able to find as of now. Please advise.

Great touch-screen scroll code!! I’m using it in a project of mine and it works really well!

Just one thing I would like to point out though, is that for the line document.getElementById(id).addEventListener("touchstart", ……….

Why not just replace document.getElementById(id) with the “el” var that was created earlier (which is probably why the variable was created in the first place)? It made no difference to me when I did that, and it shortens the code a tiny bit as well :)

Thank you for your code.
I’ve a tagboard on a forum where people paste links too.
It wasn’t working with Android, don’t know if nowadays there’s some more clean css solution but your script is still working fine, I just done some change for the two issues: “full screen” and “link click” (at least on the second click should work).

i found a small bug but with the help of a friend we were able to get it fix. What was happening is that on Android 2.2 devices that visited a website with multiple overflow rules it would scroll but not let you click with that div. We found out by removing the preventDefault from the ‘touchstart’ event let it finish and then you could both scroll and click on multiple divs. Here is the updated code:

I set up and started using your code in a website a few months back (this was actually in August of 2012). To begin with the code seemed to work fine and all my users were happy with it. However, recently I’ve found problems with getting links to work in the part of the page that’s set up to touch scroll. Therefore, please let me know if there is a way of resolving this.

The page that I’m using this feature on is the main page in an online diary. In that page I have two scrolling lists of diary entries. Each list contains entries from a different date.

A user can click or touch any of the entries in these lists to open up an edit page that allows them to modify the entry. There can be many entries in each list, each of which is set up as a link that works as I’ve described above.

Everything works fine for users on PC, etc, but users on iPad and iPhone, who need the touch-scroll feature have problems activating the links on diary entries. 99% of the time, the links just don’t work, so users on mobile devices are currently unable to access the edit pages from those devices.

I realise that this issue may have been discussed previously, but there are so many discussions listed in your page and I don’t have time to read through all of them. Therefore please give me some info on resolving this or point me to a previous discussion that does that.

Yes, the discussions here have gotten fairly large now! I honestly haven’t been following it that closely, but I believe someone here has made headway in getting links within the scrollable content to work for touchscreen devices. But, it also appears that newer version of iOS and Android have made it so that normal scrolling of these elements can be done with one finger – no extra code needed. So as a first step, maybe try just removing this code completely and testing it out then.

After submitting my question I had a read through the other questions/answers in this page. As a result of doing that I found a comment with a time & date as follows:
September 8, 2010 at 7:12 am

In that comment the writer said that removing the ‘event.preventDefault()’ line(s) in the touchstart event solved the problem so that links worked. I tried that and it really does work, so you may want to test this for yourself and maybe put a comment about it with the code at the top of the page.

I’m pretty sure you could just check for window.TouchEvent instead of creating an event like that (which is actually deprecated). Also my phone (Android 4.0) doesn’t notice scrollLeft being changed. It ignores it.

There’s quite a lot said here and I need some time to check them all out. The solution given here is still necessary. I call the function with the ontouchstart attribute of the div of the scrollable area.The problem with clickable links inside the scrollable area, I solved by simply using the ontouchend attribute, which works just as fine for links, so I don’t have to remove event.preventDefault from touchstart which creates other problems we don’t want. Hope this helps

On many android devices if preventDefault is not set on touchstart the device assumes native scrolling and stops sending touch events to webview. If preventdefault is set on touchstart all native scrolling is disabled. The only known way to overcome this behavior is to user this shim : https://github.com/TNT-RoX/android-swipe-shim

Thanks so much for the code. I used it to scroll horizontally, so modified scrollTop to scrollLeft, pageY to pageX, and seems to work great! You rock man. I love your photos too. I used to live in Nashville, TN, and some of those photos reminded me/made me miss the south!

BTW, if I could make one improvement. I would, on both lines that have the preventDefault() I would be more explicit because in my case preventing the default action killed all the anchor tags so instead I went with event.touches[0].preventDefault(); for both “touchstart” and “touchmove”