Why the Scroll Event Change in iOS 8 is a Big Deal

ios_scroll_header

If you’ve read any of the “What’s new in iOS 8 guides”, you may have noticed a change to how scroll events work. Although many may consider this a minor change, any developer that has tried to implement scrolling logic on the mobile web knows that this is actually fairly important. In this article I’ll explain what changed, what it means, and discuss one major caveat for Cordova developers.

A brief history lesson

When iOS Safari was first being developed, Apple’s engineers faced some difficult challenges displaying the existing web on a small screen. For better or worse, many of their internal engineering decisions became enshrined as “features” of the web — such as touch events, meta viewport tags, and so forth. One of these decisions was to pause all JavaScript execution whenever the user scrolled. To show this in action consider the following code that counts scroll events:

var count = 0;
window.addEventListener( "scroll", function( event ) {
    count++;
});

In the two gifs below I display the event count in a fixed header and scroll a few times. Notice that in iOS 7 (left) the count does not increment until scrolling has completely stopped, whereas in iOS 8 (right) the count grows continuously.

Example of the scroll event behavior in iOS 7Example of the scroll event behavior in iOS 8

Why exactly Apple pauses JavaScript execution during scrolls is unclear, but it’s likely for performance reasons, as the scroll event is often abused by web developers. In 2011, after a scroll event handler rendered Twitter unstable for many users, John Resig wrote an article about the scroll event problem, in which he included the following best practice:

It’s a very, very, bad idea to attach handlers to the window scroll event. Depending upon the browser the scroll event can fire a lot and putting code in the scroll callback will slow down any attempts to scroll the page (not a good idea). Any performance degradation in the scroll handler(s) as a result will only compound the performance of scrolling overall. Instead it’s much better to use some form of a timer to check every X milliseconds OR to attach a scroll event and only run your code after a delay (or even after a given number of executions – and then a delay).

So put yourselves in the shoes of an Apple engineer working on the original iPhone. A lot of sites are running performance-intensive code in scroll events and it’s making your browser seem choppy and slow. (And remember we’re talking about running on the original iPhone, not the hardware of today.) What do you do? Apparently you completely pause JavaScript execution during scroll, which is a sane decision given that environment.

Subsequent mobile browsers — notably IE Mobile and the Android browser — followed Apple’s example, possibly for similar reasons, or possibly for compatibility. Regardless, the lack of usable scroll events became a limitation of the entire mobile web for some time.

Why is this important?

As it turns out, there are many completely valid reasons you may want to perform actions during scrolling, for example parallax effects, or performance-friendly infinite scroll lists. And because of Apple’s decision to pause JavaScript during scrolling, these effects became impossible to do on the mobile web, at least without implementing scrolling with JavaScript, which a number of libraries actually do.

For example the popular iScroll library reimplements scrolling using CSS translations to make custom scroll events possible. Kendo UI Mobile includes a custom scroll widget and uses it to drive its list-based widgets. To be fair, these frameworks offer a lot more functionality than simple scroll events, and the custom JavaScript is usually done to get the best possible performance, but, the fact that you have to rebuild scrolling — a basic tenet of a web browser — to get usable scroll events on mobile is…insane.

And keep in mind that we’re not just talking about the scroll event. iOS < 8 pauses all JavaScript execution during scrolling. Therefore, any intervals you create with setInterval() are also paused. For example consider the following code that displays a new number every second:

var count = 0;
setInterval(function() {
    count++;
    document.body.innerHTML += "<p>" + count + "</p>";
}, 1000 );

In the two gifs below I start scrolling after the count hits 3. Notice that in iOS 7 (left) the count stops during scrolling, whereas in iOS 8 (right) the count continues.

Example of JavaScript execution being paused on scroll in iOS 7Example of JavaScript execution continuing on scroll in iOS 8

So, if you you use intervals in your apps for any reason, they are no longer arbitrarily paused in iOS 8 during scrolls.

Update (September 25th): Per a comment from Rick Byers my wording here is incorrect. iOS does not pause JavaScript execution—it pauses painting. So your app’s JavaScript will continue to run, but any changes to the DOM will not be painted until the scroll action completes.

How the web changed

Usually once a given behavior hits a major web browser we’re stuck with it until the end of time, but thankfully JavaScript execution during scrolling broke out of this mold. The Android team started firing continuous scroll events on the default browser shipped with Ice Cream Sandwich back in 2011. When Chrome started shipping on Android 4.0 it fired continuous scroll events as well. The next browser to change was IE Mobile, which followed suit on Windows Phone 8 back in 2012.

This left iOS as the only holdout, and with iOS 8 they have joined the rest of the mobile world, which finally gives us comprehensive coverage on the mobile web.

You get scroll events. Everyone gets scroll events.

Not pausing JavaScript execution actually adds some compatibility for iOS that they didn’t have before. For example, the popular — and fun — scrollorama jQuery plugin started working correctly as of iOS 8. The gif below shows it in action.

scrollorama

One caveat for Cordova developers

Although Apple implemented this change in iOS Safari, as well as its new WKWebView control, it did not change the scroll behavior in its old UIWebView control. And because of a major bug in the replacement WKWebView control, the Cordova team cannot upgrade to WKWebView yet.

This means that at the moment Cordova apps running on iOS 8 continue to pause JavaScript execution, and will continue to until Cordova can upgrade. And this doesn’t just affect Cordova apps. Any iOS app that uses web views — including Facebook, Twitter, and Chrome for iOS — will get the old behavior until they upgrade their apps to WKWebView. So yes, that means you could get different behavior opening the same URL from different iOS apps depending on which API they use internally.

Update (September 25th): Commenter Ben Kennedy found that, for whatever crazy reason, the old scroll behavior also applies to home screen web apps. So to summarize, apps that run in Safari or in a WKWebView get the new scroll behavior, apps that run in a UIWebView or in a home screen web app do not.

Wrapping up

With iOS 8 it’s cool that you can actually execute code while the user scrolls, and that parallax effects are now possible without complex JavaScript hacks, but to me this is cool because the web actually changed. All mobile browsers used to pause JavaScript execution on scroll, but over time, as hardware advanced, they changed to allow JavaScript to run and fire scroll events as expected. This gives me hope that other features of the mobile web can change for the better as well.

Header image courtesy of William Hook

Comments

  • Rick Byers

    Good article but it makes a common error in understand iOS scroll behavior. Javascript is not paused at all, but painting is. To see this try a setInterval that adds a timestamp to a log buffer. The log won’t get displayed until scrolling finishes, but the timestamps show that JavaScript was indeed running throughout the scroll.

    • Thanks Rick, I’m glad you clarified this. I actually didn’t know only painting was paused, so I took a minute to verify this (for my own sake) and you’re absolutely right. Apparently when I’ve ran into this in the past it always involved the DOM in some fashion. I added a big note to the article itself to point out my incorrect description of the behavior. Thanks again.

      • Technically speaking, I would even argue that the painting is not paused. This is only the effect we observe. What is likely going on is the decoupling of painting via the backing store. The web page is painted to an offscreen buffer (usually tiled as GPU texture) and the browser manipulates the buffer according to user interaction (scrolling, but also zooming).

        See my old blog post Understanding Hardware Acceleration on Mobile Browsers on this topic.

        • Thanks Ariya, this helps me understand a little more of what’s going on under the hood. You have a good point that this topic applies to zooming as well.

          • Felix

            Hey TJ, great article, thanks a lot! One remaining question: so, it is possible to determine when a scroll event has started on iOS8 home screen web app? I’m struggling making this work.

      • I would have assumed that JavaScript execution stopped as well TJ. All of the problems you mention are still persistent in the IOS version of Chrome… https://code.google.com/p/chromium/issues/detail?id=423444

        The reason I say that is because the value of “scrollTop” does not seem to update until the scrolling activity ceases. Hence the reason you can’t just set an interval to keep track of things.

    • Harsha Rama

      Hey,… I’ve tried to check by appending a string with timestamps every one second, but i’ve noticed that when i was scrolling, the js was paused, and the string printed later did not have the time stamps of those seconds…..

      Like … 02:03:44, 02:03:45,, 02:03:49,…… meaning even the buffer did not get updated… So it does look like the js is being paused…

      • Rick Byers

        I believe timer callbacks aren’t invoked either (it’s an “isolated run loop” or something like that). Try adding a timestamp whenever you receive a touchmove event.

  • Craig Francis

    Dam, I used the scroll event to stop things animating, flashing, blinking, moving, etc while I was reading… that’s really going to make me think twice about reading things on a phone/tablet now.

  • Ben Kennedy

    Does the following meta tag run the page in the old UIWebView instead of WKWebView? I am seeing the ios7 scroll event behavior in ios8 as soon as I bookmark a page to my homescreen and then open it from there.

    • Oh wow you’re right. Home screen web apps run in the main Safari browser but in some sort of different (undocumented) context. I can confirm that the scroll behavior in home screen apps is the old behavior, which is crazy. I’ll add a note about this in the article. Thanks for commenting!

      • Hey TJ, great article, thanks a lot! One remaining question: so, it is
        possible to determine when a scroll event has started on iOS8 home
        screen web app? I’m struggling making this work.

        • Hi Felix,

          Thanks! But to answer your question, unfortunately, no. For whatever reason home screen web apps and UIWebViews did not get the updated behaviors, so you cannot detect when scroll events start. As crazy as it sounds, your only real option is to reimplement scrolling in JavaScript.

          • Hey TJ, I really hoped for a definite answer — so I can stop searching for a non existing solution 😀
            Thanks!

            I guess using one of these scrolling plugin will cause new issues but I have another idea: Perhaps a native wrapper app with the new engine could be a solution for my very specific use case…

          • No problem 🙂

            A Cordova app with a WKWebView plugin (http://plugins.telerik.com/plugin/wkwebview) will give you the new scroll behavior. Cordova itself is hoping to default to a WKWebView in the near future (see https://github.com/shazron/phonegap-questions/issues/41).

          • Unfortunately I’m just building an AngualrJS app here, everything else is done by another agency — incl. all native app components of this project. Could have been so easy…

  • Pingback: Dew Drop – September 25, 2014 (#1863) | Morning Dew()

  • Pingback: Today’s Readings | Aaron T. Grogg()

  • Jan

    Great Article! I was waiting so long for this! Let’s hope android catches up as well!

  • This is an awesome step forward but in doing this Apple have created a bug whereby -webkit-overflow-scrolling:touch; now completely breaks any scrolling where this style is applied. This bug is causing me one hell of a headache!

    http://stackoverflow.com/questions/26176288/webkit-overflow-scrolling-touch-breaks-in-apples-ios8

  • Pingback: Detecting Scroll Position | Gray Ghost Visuals Press()

  • dotfury

    Interesting article, I found this while researching what I thought was a scroll issue in iOS 8, but it may be something else. I’m trying to use CSS rotateY/rotateX while scrolling and it’s now working on iOS 8. Regular rotate works. I have a stack overflow for it: http://stackoverflow.com/questions/26641595/ios-8-css-rotate-element-disappears
    Any ideas are appreciated, thanks.

  • Pingback: Responsive Sprite Animations with ImageMagick and GreenSock()

  • yang

    I want to ask, why use IScroll, use $(window). ScrollTop () to obtain the value is always 0

  • Alex

    Seems to include ALL CSS transitions / transformations too 🙁

  • obi2

    Yeah… but this is a big deal for a very different reason than you suggest: Apple updated iOS on its older model iPhones as well. Now remember, the reason Apple used to pause all javascript during scrolls was that older model iPhones would experience unacceptable slowdowns if active scroll detection was implemented.

    So now, on those same older model iPhones which couldn’t handle active scroll detection, Apple has implemented active scroll detection. In other words, they consciously slowed down older model iPhones to an extent that they previously deemed unacceptable.

    Furthermore, even the iPhone 6 can’t really handle the new technology. On many pages where active scrolling is used, you’ll see smoother scrolling on iOS7 than on iOS8. What this means is that from a user perspective, iOS8 is a ‘less-smooth’ experience. Nice job Apple.

    Yeah, it’s a big deal. It’s a big deal because Apple is getting ahead of their own processors and crippling older phones.

    There used to be a company called Microsoft that regularly pulled the same kind of crap, with bloated OS’s and system-slowing code. Look what happened to them.

  • Pingback: How to feature-detect browsers that restrict scroll events? - HTML CODE()

  • Pingback: Enable smooth scroll in UIWebView - CSS PHP()