为什么iOS 8中的滚动事件变化是个大问题


作者:   TJ VanToll  

如果你读过任何 "iOS 8的新指南",你可能已经注意到滚动事件工作方式的变化。尽管许多人可能认为这是一个小变化,但任何试图在移动网络上实现滚动逻辑的开发者都知道,这实际上是相当重要的。在这篇文章中,我将解释什么变化,它意味着什么,并讨论Cordova开发者的一个主要注意事项。

简短的历史课

当iOS Safari刚被开发出来时,苹果的工程师们面临着在小屏幕上显示现有网络的一些困难挑战。无论好坏,他们的许多内部工程决定都被奉为网络的 "特色"--如触摸事件、元视口标签等等。其中一个决定是在用户滚动时暂停所有的JavaScript执行。为了说明这一点,请看下面的代码,它对 scroll 事件进行计数。

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

在下面的两个GIF中,我在一个固定的标题中显示事件计数,并滚动了几次。注意,在iOS 7(左)中,计数不会递增,直到滚动完全停止,而在iOS 8(右)中,计数持续增长。

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

苹果到底为什么要在滚动过程中暂停JavaScript的执行还不清楚,但很可能是出于性能的考虑,因为滚动事件经常被网络开发者滥用。2011年,在一个滚动事件处理程序使许多用户的Twitter变得不稳定之后,John Resig   写了一篇关于滚动事件问题的文章 ,他在文章中提出了以下最佳做法。

给窗口滚动事件附加处理程序是一个非常、非常、糟糕的主意。根据浏览器的情况,滚动事件可能会发生很多,在滚动回调中加入代码会减慢任何滚动页面的尝试(不是一个好主意)。在滚动处理程序中的任何性能下降都会使滚动的整体性能变得更加复杂。相反,最好是使用某种形式的定时器,每隔X毫秒检查一次,或者附加一个滚动事件,只在延迟后(甚至在一定数量的执行后--然后再延迟)运行你的代码。

所以,请你们站在苹果工程师的立场上,为最初的iPhone工作。很多网站都在滚动事件中运行性能密集型的代码,这使得你的浏览器看起来很不稳定,很慢。你是怎么做的呢?显然,你在滚动过程中完全暂停了JavaScript的执行,鉴于这种环境,这是一个明智的决定。

后来的移动浏览器--特别是IE Mobile和安卓浏览器--都效仿了苹果的做法,可能是出于类似的原因,也可能是为了兼容。不管怎么说,缺乏可用的滚动事件在一段时间内成为整个移动网络的限制。

为什么这很重要?

事实证明,有许多完全合理的理由,你可能想在滚动过程中执行动作,例如视差效果,或性能友好的无限滚动列表。而由于苹果公司决定在滚动过程中暂停JavaScript,这些效果在移动网络上就不可能实现了,至少在不使用JavaScript实现滚动的情况下是这样的,而很多库都是这样做的。

例如,流行的   iScroll库 使用CSS翻译重新实现了滚动,使自定义滚动事件成为可能。Kendo UI Mobile包括一个   自定义滚动小部件 ,并使用它来驱动其   基于列表的小部件 。公平地说,这些框架提供了比简单的滚动事件更多的功能,而且自定义的JavaScript通常是为了获得最好的性能,   但是 ,你必须重建滚动--网络浏览器的基本原则--来获得移动端的可用滚动事件的事实是...疯狂的。

请记住,我们不仅仅是在谈论滚动事件。iOS < 8在滚动期间暂停   所有 JavaScript的执行。因此,你用 setInterval() 创建的任何区间也会暂停。例如,考虑下面的代码,每秒钟显示一个新的数字:

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

在下面的两个GIF中,我在数到3后开始滚动。请注意,在iOS 7(左)中,计数在滚动过程中停止,而在iOS 8(右)中,计数继续。

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

因此,如果你在你的应用程序中出于任何原因使用间隔,在iOS 8中,它们在滚动过程中不再被任意暂停。

更新(9月25日) : 根据Rick Byers的评论   我在这里的措辞是不正确的,iOS不会暂停JavaScript的执行,而是暂停绘画。因此,你的应用程序的JavaScript将继续运行,但对DOM的任何变化将不会被绘制,直到滚动动作完成。

网络是如何改变的

通常情况下,一旦一个特定的行为出现在主要的网络浏览器上,我们就会被它困住,直到时间结束,但幸运的是,滚动过程中的JavaScript执行已经突破了这个模式。早在2011年,安卓团队就开始在随冰激凌三明治出货的默认浏览器上发射连续滚动事件。当Chrome浏览器开始在Android 4.0上发货时,它也发**连续滚动事件。下一个改变的浏览器是IE Mobile,它早在2012年就在Windows Phone 8上效仿。

这使得iOS成为唯一的保留者,而随着iOS 8的出现,他们已经加入了移动世界的其他部分,这终于让我们在移动网络上有了全面的覆盖。

You get scroll events. Everyone gets scroll events.

不暂停JavaScript的执行实际上为iOS增加了一些他们以前没有的兼容性。例如,流行的--也是有趣的--  s crollorama jQuery插件 从iOS 8开始正确工作。下面的gif图显示了它的运行情况。

scrollorama

对于Cordova开发者来说,有一点需要注意

尽管苹果在iOS Safari中实现了这一变化,以及它的新的 WKWebView 控件,但它并没有改变其旧的 UIWebView 控件中的滚动行为。而且由于替代的 WKWebView 控件有一个   大的错误 ,Cordova团队   还不能升级到 WKWebView

这意味着目前在iOS 8上运行的Cordova应用程序继续暂停JavaScript的执行,并将继续下去,直到Cordova能够升级。而且这不仅仅影响到Cordova应用。任何使用网络视图的iOS应用--包括Facebook、Twitter和Chrome for iOS--都会得到旧的行为,直到他们将其应用升级到 WKWebView 。所以,是的,这意味着你可以从不同的iOS应用中获得不同的行为,打开同一个URL,这取决于他们内部使用的API。

更新(9月25日) : 评论者Ben Kennedy   发现 不管出于什么疯狂的原因,旧的滚动行为也适用于主屏幕的Web应用程序。因此,总结一下,在Safari中或在 WKWebView 中运行的应用程序得到了新的滚动行为,而在 UIWebView 中或在主屏幕网络应用中运行的应用程序则没有。

结束语

有了iOS 8,你可以在用户滚动时实际执行代码,而且现在不需要复杂的JavaScript黑客就可以实现视差效果,这一点很酷,但对我来说,这一点很酷,因为网络实际上已经改变了。所有的移动浏览器过去都是在滚动时暂停JavaScript的执行,但随着时间的推移,随着硬件的进步,它们改变了,允许JavaScript的运行,并按预期发射滚动事件。这给了我希望,移动网络的其他功能也能   向更好的方向发展