原文地址:https://developer.telerik.com/featured/scroll-event-change-ios-8-big-deal/
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:
varcount=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.
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:
varcount=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.
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.
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.
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