Hatena::ブログ(Diary)

KAZUMiX memo

2007-09-13

CSSをURIのクエリーで切り替えるJavaScript

| CSSをURIのクエリーで切り替えるJavaScriptを含むブックマーク

2008-03-06 追記: jQuery無しで動くように修正しました

あるといいかもと思って試しに作ってみたら、IE*1の印刷対応に端を発する問題でえらい苦労しました。苦労話は後回しにするとして、次が動作サンプルページとサンプルコードです。(要jQuery。要素の取得で楽してコードを短くするために使っています)

(function(){

    //クエリーにstylesheetがあれば切り替え関数呼び出し
    if(location.search.match(/\bstylesheet=([\w]+)/)){
        var titleValue = RegExp.$1;
        changeStyle(titleValue);
    }

    // CSS切り替え関数
    // 引数titleValueと一致したtitle属性を持つlink要素を有効にする
    // 一致しなかったものは無効にする
    // もともとtitle属性が無いものには何もしない
    function changeStyle(titleValue){
        //relとtitle属性の有るlinkを配列に格納
        //var links = $('link[@rel*=style][@title]');
        var head = document.getElementsByTagName('head')[0];
        var allLinks = head.getElementsByTagName('link');
        var links = [];
        for(var i=0; i<allLinks.length; i++){
            var link = allLinks[i];
            var linkRel = link.getAttribute('rel');
            var linkTitle = link.getAttribute('title');
            if(linkTitle && linkRel.match(/\bstylesheet\b/)){
                links.push(link);
            }
        }
        //すべてDOMから撤去する
        var matchedLink;
        for(var i=0;i<links.length;i++){
            links[i].disabled = true;
            links[i].parentNode.removeChild(links[i]);
            //引数と一致したものをmatchedLinkに保存する
            if(links[i].title == titleValue){
                matchedLink = links[i];
            }
        }
        if(!matchedLink)return;
        //有効にするlinkのrelをstylesheetにし、新規にDOMに追加
        var link = document.createElement('link');
        link.disabled = true;
        var attrs = matchedLink.attributes;
        for(var i=0;i<attrs.length;i++){
            var nodeValue = attrs[i].nodeValue;
            var nodeName = attrs[i].nodeName;
            if(nodeValue){
                if(nodeName=='rel'){
                    nodeValue='stylesheet';
                }
                link.setAttribute(nodeName,nodeValue);
            }
        }
        head.appendChild(link);
        link.disabled = false;
    }

})();
<link href="css/1st-impact_graphical.css" rel="stylesheet" type="text/css" title="graphical" />
<link href="css/1st-impact_simple.css" rel="alternate stylesheet" type="text/css" title="simple" />

↑こんな感じで代替スタイルシートを用意して、JavaScriptで切り替えるっていうよくあるかもしれないやつです。ただ、クエリーで切り替えたい!ってことで自分でも作ってみたんだけど、印刷にも応しようとしたらこれが結構な地雷原だったのです。上のコードで何でこんなことやってんの?な部分の理由は以下。

地雷その1「IErel="alternate stylesheet" を印刷で無視する」

例えば上の例では title="simple" のやつに切り替えてスクリーン上でそのCSSが適用されていても rel が "alternate stylesheet" のままだと印刷では無視されます。

地雷その2「じゃぁ切り替えるときに rel="stylesheet" に書き換えればよくね? → NG」

その読み込む CSS ファイルの中で @import が使われていた場合、IE ではそれが正常に読み込まれない問題が発生。(IE Developer Toolbar で確認してみると、該当 link の readyState が loading で固まってます)

地雷その3「じゃぁ DOM ツリーから一度撤去(remove)して、書き換えてから追加(append)するのはどうか → NG」

DOM ツリーから撤去した要素の属性は、IE ではリードオンリーになるらしく、書き換えようとするとエラーが発生してしまう。Firefox とか Opera は平気。

もうこの辺で嫌になって最終手段にでました。

解決編「元の link 要素を一度すべて撤去し、必要な link 要素を新たに作り、属性をコピって追加」

もちろん rel="stylesheet" にして。さすがにここまでやると IE も問題無し。大技と言うか大味というか、エレガントさがまるで感じられませんが、とりあえず今回はこれで妥協しました。

おまけ

実は link の disabled 属性を活用するともっと簡単に対応できます。

<link href="css/1st-impact_graphical.css" rel="stylesheet" type="text/css" title="graphical" />
<link disabled="disabled" href="css/1st-impact_simple.css" rel="stylesheet" type="text/css" title="simple" />

↑こんな感じで準備して、disabled を切り替える方法です。でもこれ Validator 通らないし Dreamweaver も disabled を無視するしで、とても作業がしづらくなるのでやめました。

関連エントリー

*1:基本的にIE7でチェック