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.
Feel free to fork or modify this code on GitHub.
94 replies
Dude you rock! Silly that these browsers don’t handle this type of element in a more intuitive way, considering CSS overflow is taking over what we used to use iframes for…
Thanks much for the very concise, very functional code!
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.
I’ve fixed the issue with the try/catch block. The
if(isTouchDevice()){was missing the parenthesis to run it as a function. This actually caused it to break pretty bad in IE as well.Anyway: a dumb oversight by me, but a simple fix as well.
Thank you so much for this code snippet, really useful!
Do you know any way to add kinetic/momentum scroll to the scrollable div?
Thanks!
I haven’t looked into it, but I imagine it wouldn’t be too hard to add in if you found the right algorithm.
The one flaw in this, at least for my application, is that I have clickable links inside my scrolling region that the touchstart event is capturing. So no clicking.
Ideas?
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???
Thanks for sharing Chris – this was a great solution!
As Brad pointed out, there’s a problem with links, or even nested touch events. Do you have any ideas how to cope with this?
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 normallyThanks Chris – nice solution. And Jesse for discovering how to make buttons and links work.
@Max @ChrisBarr You could use Modernizr to determine if the browser supports touch events. Off the top of my head, you could use:
if ( $(‘html’).hasClass(‘touch’) ) { touchScroll(#element); }True, and I am a fan of Modernizr, but if you only want to detect touch events (as in my use case) an entire library that does other things can be a bit overkill.
Hello, How exactly do you get the error:
…to stop on your example? In your example here: http://chris-barr.com/files/touchScroll.htm
In IE 8, if errors are turned on, it fails. Can you tell me why?
Thanks in advance.
Sorry! I made a fix to the code sample on this page but not on the demo page. It’s fixed now.
forgive me, maybe I’m stupid. I don’t see where you changed it. Help please.
Kelly
Do you know if there is a way to make this work scrolling horizontal?
Chris, this is very smart and works perfectly on the default Android (2.3.2) browser – with just one finger! Very useful – google/cyanogen/mobile webkit builders should take note!
but what about “
In Android 2.3 is not work it….only scroll…or clicking quickly…
You are officially my code hero of the day. Thank you!
Hi,
I was looking for tween/easing support of Chris function above. So I made this integration with the lightweight popular JSTweener lib:
http://pastebin.com/MRA5hQ1R
(download jstweener @ http://coderepos.org/share/wiki/JSTweener)
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)
Yayyyy!!! you’re a damned god!!! =D
for( i=0; i<1000; i++) { echo "Thank you!"; }Thanks Chris, you saved my bacon!
I just checked a site I’ve spent the entire night working on (now 6:30am) and found it doesn’t work on Android 2.2.1 browser. Even the twin finger thing doesn’t work.
Your code allows the list to scroll again. Now I just have to make something that looks like a scroll bar appear.
Thanx a lot to Chris and Jesse . :)
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); } }Thank for coding
everything is good,But no links are working(eg:
<a href="http://www.google.com">siju</a>)…What i do?,i want this link is workng cde…
Hi,
Actually this code was amazing to fix my problem but it disabled all onclick events into my page how can I enable clicks into the scrollable div can anyone help me please?
Thnx…
@meeso:
quickfix: if you removed
event.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
Ohhhhh God bless you….
Its working it….
Thanks a ton!!! This has really solved my probelm
@Jeff – awesome work extending the original (and already very cool) solution.
Thank you both.
You simply rocks !!!
Thanks :)
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:
function touchScroll(selector) { if (Modernizr.touch) { var scrollStartPos = 0; $(selector).live('touchstart', function(event) { event.preventDefault(); scrollStartPos = this.scrollTop + event.originalEvent.touches[0].pageY; }); $(selector).live('touchmove', function(event) { event.preventDefault(); this.scrollTop = scrollStartPos - event.originalEvent.touches[0].pageY; }); } }More than a year after your first post, still useful !
I love those slick and simple solutions.
Thanx a bunch !
here is a jQuery plugin to do so
$.fn.touchScroll = function(options) { var isTouchDevice = function(){try{document.createEvent(‘TouchEvent’);return true;}catch(e){return false;}}; if (isTouchDevice()) { this.data(‘scrollStartPos’,0); this.bind(‘touchstart’,function(event){$(this).data(‘scrollStartPos’,$(this).scrollTop()+event.originalEvent.touches[0].pageY);event.preventDefault();}); this.bind(‘touchmove’,function(event){$(this).scrollTop($(this).data(‘scrollStartPos’)-event.originalEvent.touches[0].pageY);event.preventDefault();}); } }Doesn’t seem to work inside a colorbox :(
Jeff_ or anyone! Is there a working example of the jQuery plugin. It does not seem to work for me
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)!!
Cheers,
Mark
Hi, i have try this for a div on a mobile website, the scroll work right but i can’t make work the link in the div =(
Great solution guys, thanks to all contributors to this thread.
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.
Thanks Chris.. this solved my problem.
This was working fantastically until ice-cream sandwich came along. Now it’s freezing.
For me it works fine but problem is I have to tap once to make it scrollable is there anyway to prevent this erraneous behaviour
please suggest
thanks in advance
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.
This worked great! Made my site work well.
Only problem is, I’ve just upgraded my Galaxy Note to Ice Cream Sandwich and it’s broken.
I’m gonna take a look into it and see what’s different between the android OS
Hey… More than 2 years since you’ve posted this and it still saves sombody’s soul (mine as example).
Thank you!
Saved my as* dude.. Thanks a millionth tone
Thanks. You’re an as* saver :)
It seems does not work on Android 4.0.4 :(
Here’s Jeff’s better solution in Jquery version, partially based on Cormac’s code:
function touchScroll(selector) { if (isTouchDevice()) { var scrollStartPosY=0; var scrollStartPosX=0; $(“body”).delegate(selector, ‘touchstart’, function(e) { scrollStartPosY=this.scrollTop+e.originalEvent.touches[0].pageY; scrollStartPosX=this.scrollLeft+e.originalEvent.touches[0].pageX; }); $(“body”).delegate(selector, ‘touchmove’, function(e) { if ((this.scrollTop < this.scrollHeight-this.offsetHeight && this.scrollTop+e.originalEvent.touches[0].pageY < scrollStartPosY-5) || (this.scrollTop != 0 && this.scrollTop+e.originalEvent.touches[0].pageY > scrollStartPosY+5)) e.preventDefault(); if ((this.scrollLeft < this.scrollWidth-this.offsetWidth && this.scrollLeft+e.originalEvent.touches[0].pageX < scrollStartPosX-5) || (this.scrollLeft != 0 && this.scrollLeft+e.originalEvent.touches[0].pageX > scrollStartPosX+5)) e.preventDefault(); this.scrollTop=scrollStartPosY-e.originalEvent.touches[0].pageY; this.scrollLeft=scrollStartPosX-e.originalEvent.touches[0].pageX; }); } }Be sure to call the function from inside
$(document).ready().Man, you rock! I thought this was impossible!
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().Also pay attention to the “fancy quotes” which are put by WordPress, you need to use standard quotes, otherwise the code will break.
Thanks guys. However, after having a second look I realised the problem was with the
scrollStartPosvariable in thetouchScrollfunction. I’ve therefore changed that so I have a separate version of that variable for each scroll list. I’ve also duplicated theaddEventListenercalls (one pair of function calls for each scroll list). Anyway, all is now working fine. Thanks againWow, Thank you so much for that great code. I had a scrolling div that didn’t work on android, of course, and this function worked perfectly for me. Great Quick fix for my project, 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 :)
clmmatteo: the scrollbars don’t exists at all on some Android versions AFAIK, but if they appear correctly on some devices it could be nice to try them. :)
Dude you rock!
Really liked that I found this for a website I administer. That is until my Droid4 got upgraded to Ice Cream Sandwich (ICS) last night. Now if it works, it is very jittery.
It’d be great if this could be modified to work on ICS.
Back to square one for me.
Thanks for this solution it is very useful!
Like SIJU (comment 24) and MEESO (comment 25), I have links and buttons in my div which won’t work anymore.
If I take off the event.preventDefault() from the touchStart block, it is not working (Although I tap on the area first).
Any help with that??
Thanks
Simply wonderful! Thank you!
Should this also work for Android 2.3.x?
I tried but was not successful …
Clemens, yes it works. At least, my version on comment #48 runs smoothly. You can try it live by scrolling a long formula on this page (on the horizontal direction of course):
http://giovani.mathesisvicenza.it/forum/topic.php?id=363
@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.
Be careful with the syntax and Happy coding !!
Cheers
Ciao Andrea,
have you tried this very code on a 2.2/2.3-Android in vertical direction?
Looks like
e.originalEvent.touches[0].pageY(and
e.originalEvent.touches[0].pageX)always return
0…Grazie mille
Clemens
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
delegatemethod 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 would actually change delegate to .on() as recommended by jQuery instead of delegate or live(which is deprecated).
Nice solution. But i think there is a usability issue with this aproach.
If you zoom in to see only the scrolable div, then you can’t get out from it becouse no further zoom out is possible.
Ideas to solve this are wellcome.
Tom.
A website with a good mobile layout shouldn’t have the need to zoom in/out. It shouldn’t even be possible if you correctly set the
viewportto block this feature.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.
Anyway, the zoom behaviour exists, and this usability problem too.
Cheers,
Tom
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?
Ciao Lazza, maybe I am dumb?( ;) ), but
http://fiddle.jshell.net/df7n8/11/show
and
http://fiddle.jshell.net/df7n8/12/show
behave alike. Both do NOT scroll (vertically) the div but the browser.
Testing on a Samsung Galaxy S2 with 2.3.6
What am I missing?
Clemens, your div does NOT have overflow. It hasn’t got a fixed height so the whole content is displayed. If you want to scroll it, then you need to have something to scroll in the first place. ;)
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.
Just to make a note, that the non touch devices which only has trackpad or trackball, this function does not interfere in that and everything works normally with clicks from trackpad.
>your div does NOT have overflow
bingo! :-)
Thx
Clemens
Great, thanks
Much appreciated.
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).
function touchScroll(id){ if(isTouchDevice()){ var el=document.getElementById(id); var scrollStartPos=0; var lastPos=0; var delta=0; // isn't really a delta var capture=false; el.addEventListener("touchstart", function(event) { scrollStartPos=this.scrollTop+event.touches[0].pageY; lastPos = event.touches[0].pageY; if (capture) { event.preventDefault(); capture = false; } },false); el.addEventListener("touchmove", function(event) { var deltaY = scrollStartPos-event.touches[0].pageY; delta = event.touches[0].pageY - lastPos; lastPos = event.touches[0].pageY; capture = !(delta < = 0 && this.scrollTop+this.clientHeight==this.scrollHeight) && !(delta >= 0 && this.scrollTop == 0); if (capture) { this.scrollTop = deltaY; event.preventDefault(); } else { } },false); } }Hi,
This may be a stupid question, but how can I change this code to work in all the divs on the site? Now I can get it to work only in one element.
Thanks!
s-m, if you’re using my version from comment #48 (which I suggest you do) you can use ANY Jquery selector, for example touchscroll(“div”) for every div present or future in a page.
Thanks Lazza!
I don’t know if it’s working yet, I don’t have an Android in my hand.. But what did you mean by “Be sure to call the function from inside
$(document).ready()” ?I meant this: Tutorials:Introducing_$(document).ready()
It doesn’t work on Samsung Galaxy S III, any ideas?
It does work on my Asus Transformer. Thx a lot for sharing this code !
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:
function touchScroll(id){ if( isTouchDevice() ){ //if touch events exist... var el = document.getElementById(id); var scrollStartPos = 0; document.getElementById(id).addEventListener("touchstart", function(event){ scrollStartPos = this.scrollTop + event.touches[0].pageY; //event.preventDefault(); <-- This line can be removed }, false); document.getElementById(id).addEventListener("touchmove", function(event){ this.scrollTop = scrollStartPos - event.touches[0].pageY; event.preventDefault(); },false); } }Hate to break it to you but that was found a few of years ago. If you read some of the previous comments, like comment #26, you would see that removing preventDefault allowed links to work. :)
Hi,
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.
Thanks for your time.
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.
Hi Chris,
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.
Thanks for your feedback