Post by Aron / Juni 7th, 2012

Optimizing “overflow:scroll” on iOS5 [UPDATE]

With the never webkit builds on iOS5 and Android ICS finally overflow:scroll works as expected. On iOS you also have the “native” scroll bounce, which is huge IMHO (with bounce I mean the ease when the scrolled viewport reaches the top/end). The app finally feels native, a big step for web apps.

It wasn’t the sluggishness and weak performance of iScroll and scrollability I was mad about, it was the wrong implementation of the bounce physics. I was always able the tell the difference of a native and a phonegap app by checking scroll views.

Get it working

Check example 1 (with iOS5+ device or simulator)

The magic happens with this line.
Without you would still have a touchmove scroll with one finger, however the “native” bounce comes with -webkit-overflow-scrolling:touch. I notices however one thing that differs from the native scroll. When the content to scroll is on top (e.g. you can’t scroll any further up) and try it anyways, the touchmove bubbles up. Try example 1 again (with iOS5+ device or simulator) In this case it bubble to the top window object which then will scroll the browser chrome. While this might be an expected behavior, it still feels not very native. Let’s fix this.

Make it even more native

Check example 2 (with iOS5+ device or simulator)

When you try the same here, you’ll notice that you can drag the scrolled content with the expected physics. This feels, well, simply amazing. I have been working with all the JavaScript scroll implementations for such a long time now and now it’s finally native. Yeah!

So how is it done?

Well, the magic is to include the scroll content into another container div, that has to have the same height as the parent scroll container and also the correct overflow settings set (-webkit-overflow-scrolling doesn’t seem to be needed). Note that the subcontainer has to have the exact same height, so no margin or padding or stuff allowed. Also note, that no JavaScript is required. This is pure css.

Here a video to illustrate the difference between example 1 and 2:

One last thing

Notice that in the previous example you can still scroll the browser chrome? Try it by dragging (touch moving) the grey background. If you have a web app (instead of a website) you might want to disable the scrolling of the window viewport entirely.

Check example 3 (with iOS5+ device or simulator)

Easy.

Listen to the touchmove event on document level and prevent the default behavior. With this setting alone scrolling would be disabled globally, also in the overflow div. That why you need to set another touchmove listener to the scroll div and stop the bubbling. This event then will never reach the document object.

UPDATE – Arg, found an edge case

Or, actually not so edge. Check a variation of the previous example (with iOS5+ device or simulator) where there are not enough item for the div to be scrollable. When you try to scroll you’ll notice, that the browser chrome scrolls. Not good.

Here corrected example 4 (with iOS5+ device or simulator)

However, it adds more markup and more JavaScript. I would appreciate a smarter solution.

8 Comments

  1. KlausJuni 7th, 2012 / 15:32 / #1665

    By preventing the event during capture phase depending on target element being inside a scrollable or not (you’d probably had to add a class name for that) we can avoid the extra container + listener. This worked for me out of the box:

    document.addEventListener(‘touchmove’, function(e) {
    if (document.querySelector(‘.scrollable’).contains(e.target)) {
    return;
    }

    e.preventDefault();
    }, true);

  2. KlausJuni 7th, 2012 / 15:40 / #1666

    Actually, no, it doesn’t work yet that way. Anyway, thanks for sharing!

  3. KlausJuni 15th, 2012 / 17:42 / #1677

    Now, based on your work I’ve created a variation that seems to not require an additional container wrapping each sub container, or at least doesn’t depend on being the same height:

    https://dl.dropbox.com/u/102772/scrollable.html

  4. AronJuni 16th, 2012 / 12:20 / #1678

    Hi Klaus, thanks for getting back.

    Isn’t your implementation the same as my first example (also w/o a container)? http://aronwoost.github.com/optimize-webkit-overflow-scrolling/1.html

    My goal was, to not letting the window viewport (e.g. to page) scroll even if the overflow div is already at top or bottom. Check the difference here: http://aronwoost.github.com/optimize-webkit-overflow-scrolling/2.html

    You see what I mean?

  5. KlausJuni 16th, 2012 / 13:23 / #1679

    Interesting. It won’t bounce on the iPad (which I tested on), but still does on the iPhone. Anyway, I figured that it doesn’t play nice with Zepto’s “tap” event.

  6. KlausJuni 16th, 2012 / 13:26 / #1680

    On the iPhone though it’s more like the entire browser chrome bounces rather then the viewport.

  7. AronJuni 16th, 2012 / 21:26 / #1681

    What iOS version do you use on the iPad? For me it works just fine on the iPad.

    And yes, I meant the browser chrome, not the window viewport.

  8. KlausJuni 18th, 2012 / 10:08 / #1683

    Misunderstandings. My variation, which I had only tested on the iPad did *not* work on the iPhone (your’s did).

    *Both* solutions do not play nice with Zepto’s build-in events like swipe because the touchmove event’s propagation is stopped before Zepto get’s a chance to handle it (on the document.body element). This is ok if you’re not relying on these and may handle them yourself, but for me this wasn’t an option. Thus I’ve refined it so that it works well with Zepto now (updated the dropbox html file), but probably still not with the iPhone (which is ok in my particular use case).

Post a comment