iOS4-5用のMobileSafari向けなposition: fixedの判定処理

position:fixedの機能的判別へのアプローチ

iOS5のリリースに伴い,Safariがposition: fixed,oveflow: auto,-webkit-overflow-scrolling:touch等のCSSプロパティに対応しました.

辛酸を舐めたことがある人なら思うのは,前述のプロパティがないためにiScroll等のライブラリを使っていた箇所をposition: fixedを使える環境ではCSSに置き換えたい,ということ.その肝心な判別処理ですが...

これらのアプローチではposition:fixedな要素を仮に配置して,試しにスクロールさせた後の要素の位置を求めることでposition:fixedのサポートを検証しています.が,少なくともiOSには通用しないみたい.

上のアプローチで期待されているgetBoundingClientRectで得られる座標は,現在の表示領域を基準とした値だけど,iOSのSafari上で実行するとドキュメント全体の領域を基準として返ってきているせい?

ここを参考に試してみる

類似の情報でクリップしていた記事があったので,参考にしてみました.リンク先のコードは試行錯誤のあとがみられたので,ちょっと整理してあります.

サンプルコード

だいたいこんなかんじみたい.

// $.fn.readyなりonloadなりDOMContentLoadedなり
jQuery.ready(function() {

    /**
     * position:fixedサポートの機能的判別 for iOS
     */
    var isSupported;

    if ( typeof document.body.scrollIntoViewIfNeeded === 'function') {

        (function () {
            var container     = document.body,
                elem          = document.createElement('div'),
                orgHeight     = container.style.height,
                orgScrollTop  = window.pageYOffset,
                testScrollTop = 20;

            elem.style.cssText = 'position:fixed;top:0px;height:10px;';

            container.appendChild(elem);
            container.style.height = '2048px';

            window.setTimeout(function() {
                elem.scrollIntoViewIfNeeded();
                isSupported = !!(window.pageYOffset === testScrollTop) || !!(window.pageYOffset === testScrollTop+1);

                container.removeChild(elem);
                container.style.height = orgHeight;

                window.scrollTo(0, orgScrollTop);
            }, 50);
            window.scrollTo(0, testScrollTop);
        }());

    } else {
        // scrollIntoViewIfNeededをサポートしないブラウザでの通常判定
        // isSupported = testee();
    }
});

画面をスクロールさせてみて差分を取るというアプローチ自体は変わりませんが,ここではscrollIntoViewIfNeededを利用して機能的に判別しています.

テスト内容

  1. 基準になるテスト要素を配置
  2. Y方向へ20pxスクロール
  3. elem.scrollIntoViewIfNeededを実行
  4. window.pageYOffsetを調べてスクロールされたかを検査
  5. 変化がなければtrue,あればfalse

scrollIntoViewIfNeededは,その要素が見えるように(Viewに入るように)スクロールするみたいです.十分なドキュメントが見つけられなかったので詳細はちょっと不明

position:fixedがサポートされていれば,要素は表示領域に追従しているため,scrollIntoViewIfNeededを実行をしても移動しない(必要がない)ので,window.pageYOffsetは変化しません.

逆にposition:サポートされていなければ要素はtop:0pxの位置にいるため,scrollIntoViewIfNeededを実行した後のwindow.pageYOffsetは0になります.

検証してみた補足

  • オリジナルのスクリプトだとタイマーは20msだけど,iPhone 3GS(iOS5)や負荷のかかった状態のF-12C(Android2.3)で判定結果がブレてしまったので,50msぐらいにしたら結果が安定
  • ちなみに大雑把な閾値は,F-12Cで30ms,iPhone 3GSで50msでした.F-12Cすごい.
  • iPhone 3GSでviewportにinitial-scale=1.0を指定しないと,scrollIntoViewIfNeededのあとのwindow.pageYOffsetが21になることがあるので,21は許容したほうが良さそう.
  • なんか安定しないのでアレな臭い

実用に耐えうるのかしら?

It seams to work, but please provide feedback in the comments. Test position fixed for iPhone | MNObeta

元ネタにも「一見して動いているように見えるけど,フィードバックほしい」とあるので,何か分かりましたらぜひ情報をいただけますと幸いでございます.

Androidとかロクに検証環境を持ち合わせてなかったので,とりあえずiOS4/5の切り分けぐらいで考えています.使えればそれに越したことはないんですが.

実用はUserAgentとかで何とかすればいい気がしつつ.何とかならんから機能判別試してる人たちいたのかな...

getBoundingClientRectさん?

ちなみに・・・

スクロール量は加算されません。 エレメントの絶対座標値を取得する (getBoundingClientRect) - 新JavaScript例文辞典

矩形の範囲を算出するときは、表示領域でのスクロール量が考慮されます。 getBoundingClientRect - MDN

なにが正しいのかよく分からなくなってきて混乱してるところです.X-(