jQuery 1.9 で $.browser が使えなくなってしまった対策

jQuery 1.9 になってとうとう $.browser が使えなくなってしまいましたね。 もともと非推奨ということもあり、個人的にはプラグインを実装する時などは極力使用しないようにしてましたが、実際使えなくなってしまうと、動かなくなってしまうプラグインって結構多いんじゃないかなぁって気がします。

スムーススクロールが動かない

プラグインではありませんが、最近ではいろんなサイトで見かけるようになったスムーススクロールの実装で、影響受けてるというエントリを見かけました。

IE系だとウィンドウのスクロールを制御するとき「html」を指定する。 GoogleChrome や Safari などのWEBKIT系は「body」指定しないといけない。 そこで、「$.browser.safari」をつかって、どっちを指定するかを切り替えていた。 ところがどっこい、jQuery1.3以降だと「$.browser」がNG※。非推奨になっている。いつのまに!?
「jQuery.browser.safari」が非推奨になっていたので、代替案でスムーススクロール – Stainless Note

webkit 系だと html 要素では scrollTop が効かないので body 要素でスクロールさせるというやつです。

  1. $($.browser.safari ? 'body' : 'html').animate({scrollTop:100});

エントリでは jQuery に頼らず直接 userAgent を参照し解決してるようです。

$.browser に頼らないブラウザ判別

jQuery の開発元では $.support による判別を推奨してるようなのですが、このケースの場合 $.support の何を見て判別すれば良いのでしょうか? 正しい使い方なのかよく分かりませんが、以下のまとめ記事を見る限り $.support.checkOn で webkit か否か判定できるようです。

if文の条件式の中で!をつけたので、checkOnがfalseであればこの条件文に該当します。該当するのはwebkitだけなので、これでchormeとsafariのみに振り分けたコードを記述できます。なお、Android標準ブラウザ、iPhone / iPad標準ブラウザといったスマートフォンも同じChromeとSafariなので、この条件に合致します。
jQuery.supportでのブラウザ判別 – World Wide Web Guide

ただ(上記エントリのどの部分を指してるのか分かりませんが)現時点では「情報が古くなったために正しく動作しなくなりました」とのことで、以下記事の方法で判別されてるようです。

jQuery.supportだけで代表的なブラウザの判別を行うことができなくなってしまったので、UA情報には依存しないブラウザがサポートしている機能だけでブラウザ判別を行う試みです。
JSでのUA条件分岐便利スニペット – World Wide Web Guide

ちなみに $.browser や userAgent で IE7 以下のバージョン判定をする場合は、IE8 以降の機能にある互換表示設定(ツールバーの[ツール]→[互換表示設定])に注意が必要になります。以下のように document.documentMode や document.compatMode を参照しないと正確な判定ができません。(IE のレンダリングバージョンの指定と判定方法の自分なりまとめ – CYOKOLOG

  1. engine = null;
  2. if (window.navigator.appName == "Microsoft Internet Explorer")
  3. {
  4. // This is an IE browser. What mode is the engine in?
  5. if (document.documentMode) // IE8 or later
  6. engine = document.documentMode;
  7. else // IE 5-7
  8. {
  9. engine = 5; // Assume quirks mode unless proven otherwise
  10. if (document.compatMode)
  11. {
  12. if (document.compatMode == "CSS1Compat")
  13. engine = 7; // standards mode
  14. }
  15. // There is no test for IE6 standards mode because that mode
  16. // was replaced by IE7 standards mode; there is no emulation.
  17. }
  18. // the engine variable now contains the document compatibility mode.
  19. }
  20. alert(engine);

使いたい機能が機能するかで判定

適用できるケースが限定されるのかもしれませんが、自分の場合、プラグインを実装する時などは「使いたい機能が機能するか」で判定し処理を振り分けるようにしています。

例えばスムーススクロールの実装では、本質的には webkit かどうかを知りたいのではなく、スクロールさせることができる要素が html か body かを知りたいだけなので、実際にスクロールするか否かを試します。

  1. //html 要素でスクロールするか試す
  2. var isHtmlScroll = (function(){
  3. var html = $('html'), top = html.scrollTop();
  4. var el = $('<div/>').height(10000).prependTo('body');
  5. html.scrollTop(10000);
  6. var rs = !!html.scrollTop();
  7. html.scrollTop(top);
  8. el.remove();
  9. return rs;
  10. })();
  11.  
  12. //スムーススクロール
  13. $(isHtmlScroll ? 'html' : 'body').animate({scrollTop:100});

毎回書くのが面倒くさかったり、判定項目がたくさん必要で名前空間が気になるよう場合は、以下のようにプラグイン化してもいいかもしれません。

まず、汎用的に使用できるコンストラクタを用意します。念のため body が 非表示の場合は、判定処理の際、一旦表示状態にします。

  1. $.isEnabled = function(){
  2. var o = this, callee = arguments.callee;
  3. if(!(o instanceof callee)) return new callee(o,arguments);
  4. var body = $('body'),isHide = body.is(':hidden');
  5. !isHide || body.show();
  6. for(var i in o) if(typeof o[i] == 'function') callee[i] = o[i]();
  7. !isHide || body.hide();
  8. }

必要に応じて判定ロジックを $.isEnabled.prototype に追加していきます。

html 要素でスクロール可能か?

  1. $.isEnabled.prototype.htmlScroll = function(){
  2. var html = $('html'), top = html.scrollTop();
  3. var el = $('<div/>').height(10000).prependTo('body');
  4. html.scrollTop(10000);
  5. var rs = !!html.scrollTop();
  6. html.scrollTop(top);
  7. el.remove();
  8. return rs;
  9. }

position:fixed が有効か?

  1. $.isEnabled.prototype.positionFixed = function(){
  2. var el = $('<div/>').add('<div/>').css({height:8,position:'fixed',top:0}).prependTo('body');
  3. var rs = el.eq(0).offset().top == el.eq(1).offset().top
  4. el.remove();
  5. return rs;
  6. };

display:inline-block が有効か?

  1. $.isEnabled.prototype.displayInlineBlock = function(){
  2. var el = $('<div/>').add('<div/>').css({height:8,display:'inline-block'}).prependTo('body');
  3. var rs = el.eq(0).offset().top == el.eq(1).offset().top
  4. el.remove();
  5. return rs;
  6. };

以下のように使用します。

  1. //判定処理の実行
  2. $.isEnabled();
  3.  
  4. if($.isEnabled.htmlScroll) ...
  5.  
  6. if($.isEnabled.positionFixed) ...
  7.  
  8. if($.isEnabled.displayInlineBlock) ...

スマフォの普及もありデバイスが多様化してるので、極力 userAgent に依存した判定は避けたいなぁと思ってます。