iOS6ではページをスクロール中に発生したタッチイベントからJavaScriptのタイマーがセットされない?

このブログ記事で問題にしているiOSのバグは、iOS6.1ですでに修正されています

– – – – – – –

先日公開したCOSUMIのHTML5版には、「iPad3でページをスクロール中に碁盤や碁盤の周りのクリッカブルな所をタップするとその後の動作がおかしくなる」という不具合がありました。私もいまだによく分かっていないところがたくさんあるのですが、原因とそれに対してとった対策を簡単に書いておきたいと思います(このブログ記事には私の勘違いが含まれている可能性が高いです。すべてを鵜呑みにはしないでください)。

この不具合に気づいた時、まず最初に思ったのは、これはフェードイン・フェードアウトが絡んでいるのでは、ということでした。スクロール中のフェードイン・フェードアウトは、描画がたいへんなのではないかなと。しかし、必ずしもそうだと言い切れないような感じだったので、少しネットで検索してみると、こんな情報が見つかりました。

javascript – setInterval pauses in iphone/ipad (mobile Safari) during scrolling – Stack Overflow
http://stackoverflow.com/questions/11177774/setinterval-pauses-in-iphone-ipad-mobile-safari-during-scrolling

おおこれだと思いました。jQueryの.fadeIn()/.fadeOut()はJavaScriptのsetIntervalを使っているようです。しかしちょっと腑に落ちないのは、なぜタイマーが止まっただけで、その後の動作がおかしくなるのかです。再開さえしてくれれば問題無いはずですが… そこで、もう少し調べてみると、こちらのブログのコメント欄に、ちょっと気になることが書いてありました。

There is one more bug I haven't seen anybody writing about. If a touch event causes a setTimeout, but that same touch event causes scrolling, that timer will never fire.

More information and a workaround here: https://gist.github.com/3755461

あれっ? なんかおかしい… 「止まる」のではなく「発火しない」と書いてあります。そしてリンク先のios6-timers.jsをCOSUMIのに試させてもらうと、不具合が完全に起こらなくなりました。

簡単なサンプルページを用意してみました。モバイル端末から試してみてください。Startボタンをタップすると、赤と青の四角が右に動きます。赤い方はJavaScriptのsetIntervalを使っていて、青い方はCSSのtransitionを使ってのアニメーションです。

http://www.perfectsky.net/misc/20121002.html

<!DOCTYPE HTML>
<html>
<head>
<meta name="viewport" content="width=640px">
<title>iOS6 Timer Test</title>

<style>
div{
  position : absolute;
  left     : 20px;
  width    : 100px;
  height   : 100px;
}
#div1{
  background-color : #ee4444;
  top              : 20px;
}
#div2{
  background-color : #6666bb;
  top              : 140px;
}
button{
  display   : block;
  margin    : 300px auto 0;
  font-size : 200%;
}
</style>

<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script>
$(function(){

  var pos = 20;

  $("#div2").css("transition", "left 10s linear");

  $("button").on((window.ontouchstart === null) ? "touchstart" : "mousedown",
                 function () {

                   $(this).remove();

                   var timerId = setInterval(
                     function(){
                       $("#div1").css("left", ++pos);
                       if(pos >= 520){
                         clearInterval(timerId);
                       }
                     }, 20);

                   $("#div2").css("left", "520px");

                 });
});
</script>

</head>
<body>
<div id="div1"></div>
<div id="div2"></div>
<button type="button">Start</button>
</body>
</html>

まず最初は、Startボタンをタップして赤と青の四角が右に動き始めたらそこでページをスクロール(ピンチイン/ピンチアウトでもいいです)してみてください。たぶん、両方の四角が停止すると思います。そしてスクロールしていた指を離すとまた動き始めます。これは、iOSやAndoidではよく知られた挙動のようです。

そして次は、まずページをスクロールし、その指を離さないままStartボタンを押して、最後にスクロールしていた指を離してみてください。私の持っているiOS6のiPad3では(SafariでもChromeでも)、下の青い四角は動きだしますが、上の赤い四角は動き始めません。これはどうやらiOS6のバグのようです。そして先ほどのios6-timers.jsを使うとこの不具合は起きません。

肝心のCOSUMIへの対策ですが、最初はjquery.animate-enhanced pluginを使えばそれだけで直るかなと思ったのですが、よくは調べてませんがそれではうまくいかなかったので、最終的にはjQueryの.fadeIn()/.fadeOut()をすべて.show()/.hide()に書き換えることによって解決しました。他にも書き換えないといけないところがありそうでしたが、とりあえずCOSUMIの場合は、すべてjQueryの.fadeIn()/.fadeOut()に絡んでいたようです。ios6-timers.jsの使用は副作用があってもいやなのでちょっと控えました。将来的にはできればもうちょっとましな対策をしたいと思いますが、とりあえず今はこれでいきます。

今回のバグは、ページのスクロール中という限られた状態でしか発生しないためか、当初は全くといっていいほど情報がありませんでしたが、すこしずつ気づいた人がでてきたようです。

javascript – iOS 6 safari, setInterval doesn't get fired – Stack Overflow
http://stackoverflow.com/questions/12683510/ios-6-safari-setinterval-doesnt-get-fired

setTimeout while scrolling in iOS 6 doesn’t work, works in iOS 5 – Google グループ
https://groups.google.com/forum/?fromgroups=#!topic/phonegap/3Xe_M79qHEM

[追記 2013/1/29]
今回のiOSのバグは、iOS6.1で修正されたようです。COSUMIの方もまた今度元に戻しておきます。

[追記 2013/2/7]
.fadeIn()/.fadeOut()に戻しておきました。たったこれだけのことで、リッチな感じになるんですよね。