iOS6 スクロール中のタイマー発火絡みのバグ備忘

Fxck iOS6!!!

と思っている方々も少なからずいらっしゃるのではないでしょうか。ぼくは今わりとそう思っています。

だって、Androidでposition: fixedとか、z-indexとか、怪しげなモノって使わずに避ける、慎重に触れるとかまだ何とかなるじゃないですか。伝家の宝刀「Androidですから…」って言い訳も少なからず使えますし。けれども、AjaxとかTimer周りって、必須すぎて避けようがないんですよ。任意のタイミングで再描画起こすためにsetTimeoutとか使いますよね。遅延描画的な溜め込みとか、ほら、いろいろ。しかもそれ、iOSの最新版ですよ。

そんなわけで、ぼくはiOS6があまり好きではありません。(前置き)

でまぁ、Ajaxについては下記によくまとまっていますし、9月ごろの情報ですね。

今回はTimer話。

スクロール中にTimer類がセットされない

本題のタイマー類。

前提

スクロール中にタイマー類が止まる話ではありません。

↑こういうの。iOS6の問題じゃなくて、モバイル系でわりと共通してる仕様という認識。

問題

スクロール中に新しいタイマーがセットされないんです。

PageViewないしMainView的なモノのrender()に、下記のようなコード(大分簡略化してるので雰囲気で感じ取ってください)を利用していました。これが画面内のA要素をclick/tapすることで、Viewの切り替え発生時に実行されるシチュエーション。

// Backbone.View的な
render: function() {
  // アレ
  var that = this;

  // 要素をhiddenして〜
  this.$el.css('visibility', 'hidden');

  // 描画発生&残りの処理をタイマーに逃がして〜
  setTimeout(function() { // ← これが実行されない!!

    // 見えないうちに瞬間スクロールして〜
    window.scrollTo(0, 0);

    // presenterメソッドからHTMLうけとって〜
    that.$el.html(that.presenter());

    // visibleにもどす!
    that.$el.css('visibility', 'visible');

  }, 20); // このあたりは分解能の限界如何だわさ
},

で、慣性スクロール中にリンクをタップすると、画面遷移が発生 → 上記のrenderメソッド実行 → 画面まっしろなまま終わる!! という事案が発生していた次第。

調べてみると、そもそもsetTimeoutの中が実行されておらず、完璧にsetTimeout()が無視されているみたい。

StackOverflowに訊いてみても同様の報告があがっていました。

影響範囲

タイマーがしぬ、ってのは影響範囲が小さくないわけで。上にあげたような用途は地味なほうで、本来はアニメーション系が特に引っかかりそうな印象です。jQueryのanimateメソッドなぞも、jQuery.fx.timer的にsetInterval()を利用したタイマーアニメーションによる実装であることを鑑みまして、モロに該当するでしょう。

未検証な感想: Zeptoだと、zepto/src/fx.js at master · madrobby/zepto · GitHubを見る限りCSS使ってるからわりと平気そう。durationが0以下のときにゼロタイマーで発火させてるから、そのあたりは怪しい。

almondの中の擬似asyncなsetTimeout()も引っかかっていました。あるViewを呼び出す際に、毎度require()してる部分があって、それもタイマー挟んだ呼び出しが慣性スクロール中に行われて止まってた。

解決

と、大変ウザい事この上ありませんが、@cssradarのひとに教えてもらったスニペットコードでさっくり解決。

This library re-implements setTimeout, setInterval, clearTimeout, clearInterval for iOS6.
iOS6 suffers from a bug that kills timers that are created while a page is scrolling.
This library fixes that problem by recreating timers after scrolling finishes (with interval correction).
This code is free to use by anyone (MIT, blabla).
Author: rkorving@wizcorp.jp
iOS6 webkit timer bug workaround — Gist

最悪、じぶんでこういうコード書かないとダメかしら…と思っていた折なので良かった。いや、あまり良くはないのだけど。解決して良かった。

ちなみに本格的にノンディレイでよろしければ、setZeroTimeout()なるpostMessage実装を利用してもよいでしょう。以下参考URL。asyncのぶっ放しについては、setZeroTimeoutはちょっと古いスニペットな気がしてきています。他の代替手段がありそうなので別途調べます。