Me!

TJ VanToll

onscroll Event Issues on Mobile Browsers

| Comments

All browsers fire an onscroll event on the window object whenever the window is scrolled. On desktop browsers this event is fired continuously as the user scrolls, but on most all mobile browsers the event is not fired until the scrolling action comes to a complete stop.

You can see this by scrolling in the example below:

onscroll tester Open in New Window

The onscroll event count and the value of window.scrollY (the number of pixels the document has been scrolled vertically) displayed on the top of the screen in the example are updated in an onscroll event handler.

If you’re viewing this on any desktop browser you’ll see that as you scroll the onscroll event is continuously firing, window.scrollY is continuously updating, and the blue box (which is present so you can visibly tell whether the browser re-paints the screen) is continuously moving.

Enter Mobile

If you try the same demo on iOS Safari (5.0), the default Android browser <= 2.3, Opera Mobile, or IE on Windows Phone 7 you’ll notice something quite different. As you scroll the onscroll event isn’t fired, window.scrollY isn’t updated, and the blue box does not move until the scrolling has come to a complete stop.

You can see this in the video below (the video shows iOS Safari but the same behavior occurs in the other listed browsers):

Why

These mobile browsers simply do not fire the onscroll event until scrolling has completely stopped. This includes not only the touch based scrolling itself, but additionally any momentum that the user gives on the scroll. The event will not fire until it stops. This is a problem if you want to apply a visual change to the screen as the user scrolls.

Other Mobile Browsers

Firefox for Android does fire the onscroll event and updates window.clientY as you scroll, but strangely it doesn’t re-paint the screen for any changes that have been applied.

The Android browser in Ice Cream Sandwich fires the event but doesn’t feel very responsive and only sporadically re-paints the DOM to move the blue box. Luckily, Jelly Bean’s Android browser handles this example perfectly; everything is updated and rendered smoothly as the user scrolls.

The Problem

In my case I wanted to apply a change to the DOM for every pixel that the user scrolled, exactly like moving the blue box in the example above.

So the question is, can we work around this limitation and get desktop onscroll functionality in a mobile friendly way?

Workaround Attempt - setInterval

My first attempt was to set an interval that did what I wanted to do in the onscroll event. Yes the code will run continuously instead of just when the user scrolls, but it’s somewhere to start.

setInterval(function() {
	// Logic
}, 20);

The problem with this approach is that iOS Safari, Android <= 2.3, and Opera Mobile do not run any functions queued through setInterval or setTimeout while a scroll is being performed. The execution will simply be paused until the scroll has completed.

Here’s an example that simply appends an asterisk to a div every 500 milliseconds using setInterval:

setInterval and onscroll Open in New Window

If you’re viewing this in a desktop browser and you scroll, you can see that the asterisks will continue to be created.

However, on the affected mobile browsers (iOS Safari, Android <= 2.3, Opera Mobile), because the function queued through setInterval is paused, asterisk creation stops the moment you start scrolling and doesn’t resume until you stop.

This video shows this behavior on iOS Safari (5.0):

This example works perfectly (scrolling doesn’t stop asterisk creation) on the default Ice Cream Sandwich / Jelly Bean browser, Firefox for Android, and IE for Windows Phone 7.

Workaround Attempt 2 - Use Touch Events

Since the setInterval approach failed on the big mobile browsers my next thought was to use touch events instead.

Most mobile browsers fire Apple’s flavor of touch events as the user interacts with the screen via touch (the notable exception being Window’s Mobile since Microsoft has their own touch model).

In particular the ontouchmove event is fired as the user moves their finger (or stylus, etc) across the screen. Since users on touch devices need to move their finger across the screen to scroll, this seemed like the perfect alternative to onscroll.

Therefore I modified my example to use ontouchmove instead of onscroll:

Using touchmove Open in New Window

If you scroll on the above example on a desktop browser nothing will be updated since the counters are being driven by the ontouchmove event. On mobile browsers a wide variety of things happen:

  • Android: The ontouchmove event does get fired as the user moves the screen. However the DOM updates are very sporadic and feel very jerky. This is true of the default Android browser in Gingerbread, Ice Cream Sandwich, and Jelly Bean although it gets better in later versions.
  • Firefox for Android: The ontouchmove events fires but DOM updates made in the ontouchmove event take effect sporadicly if at all. Everything feels very jerky at best.
  • Opera Mobile: ontouchmove events occur but DOM changes are not applied until scrolling is complete.
  • iOS Safari: On ontouchmove event is fired as the screen is moved and the DOM does get re-painted. This is only mobile browser where this approach made a substantial difference.

One consistent issue with this approach is that the ontouchmove event is only fired when the user’s finger remains on the screen. Meaning, if the user gives any momentum to the scroll, ontouchmove events will not be fired while the window is scrolling and their finger is not on the screen.

You can see this in the video below:

So what does all of this mean about using the ontouchmove event to mimic desktop onscroll functionality? At the moment there are too many inconsistencies to rely on this behavior in any way. If you only need to support iOS Safari this approach works reasonably.

Workaround Attempt 3 - Don’t Really Scroll

Another solution out there is to disable native scrolling altogether and use JavaScript to mimic scrolling instead.

$('window').on('touchmove', function(event) {
    //Prevent the window from being scrolled.
    event.preventDefault();

    //Do something like call window.scrollTo to mimic the scrolling
    //request the user made.
});

Unfortunately such techniques are usually utilized to create fixed height/width scrolling areas and are not intended (nor especially practical) for full screens. If you are only interested in a scrolling event for a small section of the page you might want to look into something such as iScroll 4.

Conclusion

Unlike desktop browsers, most all mobile browsers simply do not fire an onscroll event until the scrolling action comes to a complete stop.

The only mobile browser that handled this event elegantly in my testing was Android’s Jelly Bean browser. Therefore, if you need any sort of cross browser support you’re simply out of luck; there is simply no cross browser viable workaround to mimic the desktop behavior. If you have had success implementing this by some other means please let me know in the comments.

Disclaimer

I haven’t been able to test this in Chrome for Android and I know there are other mobile browsers that I’m missing. If someone else has this capability I’d love to know how they handle these situations.

Also while I did verify these findings on physical devices for Firefox for Android, Android 2.3’s default browser, and Safari on iOS 5; the rest of my testing was limited to simulators / emulators. From past experience I know that simulator / emulator testing is no substitute for the real thing. Therefore, if you find any discrepancies in my findings please let me know in the comments so I can update the post.

Comments