ブラウザやOSのCSSクラスを追加するJavaScript「CSS Browser Selector」を読み解く

angry数年前の自分なら読めなかった感が漂っていたので、初心者向けにちょっと読み解いてみようという記事です。中級以上の方にはアクビの出る内容だと思います!ごめんね!

「CSS Browser Selector」は、JavaScriptで<html class=” webkit chrome win js”>のようにクラス定義してくれます。 CSSハックを使わなくてもブラウザ毎に分離して記述できるので便利です。

「CSS Browser Selector」にはPHP版も用意されており、そちらを読み解くのはあまり難しくないのですが、JS版は幾分合理的なソースになっています。

スポンサーリンク

 

圧縮されてるのを整形

まずは公式のGitHubからダウンロード。 rafaelp/css_browser_selector · GitHub

下がソースになります。勉強目的で掲載してますが、実際に使う人はこれは使わないで公式から最新を落として下さい。

/*
CSS Browser Selector v0.4.0 (Nov 02, 2010)
Rafael Lima (http://rafael.adm.br)
http://rafael.adm.br/css_browser_selector
License: http://creativecommons.org/licenses/by/2.5/
Contributors: http://rafael.adm.br/css_browser_selector#contributors
*/
function css_browser_selector(u){var ua=u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1},g='gecko',w='webkit',s='safari',o='opera',m='mobile',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.5')?g+' ff3 ff3_5':is('firefox/3.6')?g+' ff3 ff3_6':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('blackberry')?m+' blackberry':is('android')?m+' android':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?m+' j2me':is('iphone')?m+' iphone':is('ipod')?m+' ipod':is('ipad')?m+' ipad':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win'+(is('windows nt 6.0')?' vista':''):is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js']; c = b.join(' '); h.className += ' '+c; return c;}; css_browser_selector(navigator.userAgent);

コメントを除いたソースは圧縮されて1行になっており、そのままでは目で追うだけで修行コースなので、再び人間が読めるように整形します。JSを整形してくれるWEBサービスは沢山ありますが、今回はこちらを使いました。

Online JavaScript beautifier

整形したらこんな感じ。

function css_browser_selector(u) {
    var ua = u.toLowerCase(),
        is = function (t) {
            return ua.indexOf(t) > -1
        }, g = 'gecko',
        w = 'webkit',
        s = 'safari',
        o = 'opera',
        m = 'mobile',
        h = document.documentElement,
        b = [(!(/opera|webtv/i.test(ua)) && /msie\s(\d)/.test(ua)) ? ('ie ie' + RegExp.$1) : is('firefox/2') ? g + ' ff2' : is('firefox/3.5') ? g + ' ff3 ff3_5' : is('firefox/3.6') ? g + ' ff3 ff3_6' : is('firefox/3') ? g + ' ff3' : is('gecko/') ? g : is('opera') ? o + (/version\/(\d+)/.test(ua) ? ' ' + o + RegExp.$1 : (/opera(\s|\/)(\d+)/.test(ua) ? ' ' + o + RegExp.$2 : '')) : is('konqueror') ? 'konqueror' : is('blackberry') ? m + ' blackberry' : is('android') ? m + ' android' : is('chrome') ? w + ' chrome' : is('iron') ? w + ' iron' : is('applewebkit/') ? w + ' ' + s + (/version\/(\d+)/.test(ua) ? ' ' + s + RegExp.$1 : '') : is('mozilla/') ? g : '', is('j2me') ? m + ' j2me' : is('iphone') ? m + ' iphone' : is('ipod') ? m + ' ipod' : is('ipad') ? m + ' ipad' : is('mac') ? 'mac' : is('darwin') ? 'mac' : is('webtv') ? 'webtv' : is('win') ? 'win' + (is('windows nt 6.0') ? ' vista' : '') : is('freebsd') ? 'freebsd' : (is('x11') || is('linux')) ? 'linux' : '', 'js'];
    c = b.join(' ');
    h.className += ' ' + c;
    return c;
};
css_browser_selector(navigator.userAgent);

ずいぶん読みやすくなりました?一行だけ長いままですが、さっそく読み解いてみます。

 

ユーザーエージェントを解析する関数が定義されてる

構成としてはcss_browser_selectorという関数が定義され、それを実行している形となります。css_browser_selectorの実行時に引数でnavigator.userAgentを与えていますが、ここにユーザーエージェントが入っています。ユーザーエージェントとは、例えばこんな文字列です。

Chrome
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.218 Safari/535.1
FireFox
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0.1) Gecko/20100101 Firefox/6.0.1
IE
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; AskTbFXTV5/5.12.2.16749; .NET4.0E)

この文字列からユーザーのOSやブラウザ情報を読み取り、クラス名を追加するのがcss_browser_selector関数の役割です。

 

css_browser_selectorの中身

それでは関数の中を見ていきましょう。3つのパートに分けて説明します。最初のパートはこちらになります。

function css_browser_selector(u) { // uにはユーザーエージェントが入ってくる
    var ua = u.toLowerCase(), // uを全部小文字に
        is = function (t) { // isという関数定義
            return ua.indexOf(t) > -1 // uaにtが含まれているかどうか
        }, g = 'gecko', // 以下、普通の変数定義
        w = 'webkit',
        s = 'safari',
        o = 'opera',
        m = 'mobile',
        h = document.documentElement, // HTMLなら<html>を指す

全部、変数の定義です。varでローカル変数を定義しています。ポイントは , を使って同時に複数の変数を定義してる点。’gecko’とか’safari’とか文字列が定義されていますが、これらの文字列は次のパートで複数箇所で使われるので定義されています。もし複数箇所で使われないのなら定義する必要性は低いです。

次のパートは肝なのですが、整形したのに一行のままで読みにくさを保っています。

b = [(!(/opera|webtv/i.test(ua)) && /msie\s(\d)/.test(ua)) ? ('ie ie' + RegExp.$1) : is('firefox/2') ? g + ' ff2' : is('firefox/3.5') ? g + ' ff3 ff3_5' : is('firefox/3.6') ? g + ' ff3 ff3_6' : is('firefox/3') ? g + ' ff3' : is('gecko/') ? g : is('opera') ? o + (/version\/(\d+)/.test(ua) ? ' ' + o + RegExp.$1 : (/opera(\s|\/)(\d+)/.test(ua) ? ' ' + o + RegExp.$2 : '')) : is('konqueror') ? 'konqueror' : is('blackberry') ? m + ' blackberry' : is('android') ? m + ' android' : is('chrome') ? w + ' chrome' : is('iron') ? w + ' iron' : is('applewebkit/') ? w + ' ' + s + (/version\/(\d+)/.test(ua) ? ' ' + s + RegExp.$1 : '') : is('mozilla/') ? g : '', is('j2me') ? m + ' j2me' : is('iphone') ? m + ' iphone' : is('ipod') ? m + ' ipod' : is('ipad') ? m + ' ipad' : is('mac') ? 'mac' : is('darwin') ? 'mac' : is('webtv') ? 'webtv' : is('win') ? 'win' + (is('windows nt 6.0') ? ' vista' : '') : is('freebsd') ? 'freebsd' : (is('x11') || is('linux')) ? 'linux' : '', 'js'];

ここでは、ユーザーエージェントの文字列の中に、特定の文字(msieとかfirefoxとかmacとかwinとか)が含まれているかを、正規表現や先ほど定義していたis関数を使ってダダダっとチェックして、含まれていた場合は、そのクラス名を配列の要素として定義しています。下の2つの構文を使っています。

b = [];
配列を定義する
?:
JS唯一の三項演算子である条件演算子。if文みたいなもん。 a ? b : c で if ( a ) b; else c;

イメージとしては、配列定義の中にif文がズラッと並んでおり、特定の文字が含まれていた場合は配列に定義、含まれてなかったら次のif文に流れている感じ。最終的には、

b = ['webkit', 'chrome', 'win', 'js'];

のように配列を定義した状態になります。このスクリプトが走ったということはJavaScriptが動作しているという事なので、最後のjsは無条件に追加されています。

最後のパートは特に難しくないです。

  c = b.join(' '); // 配列に定義されたclass名をスペースで繋げる
  h.className += ' ' + c; // htmlのclassに追加する
  return c; // いちおう結果を返す
};

最初からhtmlにクラスが設定されていた場合に備え、連結しないようにスペースを先頭に追加しています。

PHP版だと同じ事をif文で定義しているので、ずっと読みやすいです。もし時間がある人は比べてみると面白いと思います。

PHP CSS Browser Selector

 

 

スポンサーリンク