この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

JavascriptでCSSの@importを処理する

(2012/04/30追記:IEではCSS.getRuleで@importを処理できていなかった問題を修正しました)


Javascriptで既存のCSSクラスなどを変更する場合、大きく分けて2つの手段があります。

  1. styleタグをheadの末尾に付け足して、その内容にて既存のCSSクラスなどをオーバーライドする
  2. オーバーライドではなく、既存のCSSそのものの内容を書き換える


(1)の方法が一番簡単なのですが、例えばマウスの動きにより動的にスタイルを変更する処理があった場合、
styleタグが無制限に増えていくことになり、1ページでの滞在時間が長い画面だとメモリ不足を引き起こしかねません。


というわけで(2)の方法を探してみたのですが、
割と四苦八苦した結果、最終的にはこんなコードに落ち着いた感じです。
@importの対応が、調査にもっとも時間を要しました。うへえ。


このスクリプトは、
http://ash.jp/web/css/js_style.htm
を参考にさせて頂きました。

動作確認にはjQuery1.7.xを使いましたが、わりと古いバージョンでも動くはずです。($.eachしか使ってない)

"use strict";

var CSS = {};

/**
 * スタイルを追加する。
 * 
 * @param {string}
 * selector
 * @param {string}
 * declaration
 */
CSS.addRule = function(selector, declaration) {
    var isMSIE = /* @cc_on!@ */false;
    var sheet;

    if (document.styleSheets.length) { // 最後のスタイルシートを取得

        sheet = document.styleSheets[document.styleSheets.length - 1];
    } else { // StyleSheetがない場合、StyleSheetを作成

        if (isMSIE) { // for IE8, Sleipnir

            sheet = document.createStyleSheet();
        } else { // for FireFox, Opera, Safari, Crome

            var head = document.getElementsByTagName('head')[0];
            if (head == null) {
                return;
            }

            var style = document.createElement('style');
            head.appendChild(style);
            sheet = style.sheet;
        }
    }

    if (isMSIE) { // for IE8, Sleipnir

        sheet.addRule(selector, declaration);
    } else { // for FireFox, Opera, Safari, Crome

        sheet.insertRule(selector + '{' + declaration + '}', sheet.cssRules.length);
    }
};

/**
 * スタイルを取得する。
 * 
 * @param {string}
 * selectorText
 * @return {CSSStyleRule}
 */
CSS.getRule = function(selectorText) {

    var lastFoundRule = null;

    var traverseCSS = function(sheet) {
        $.each((sheet.cssRules || sheet.rules), function() {

            var rule = this;

            // @importだったら再帰処理
            if (rule.type == CSSRule.IMPORT_RULE) {
                traverseCSS(rule.styleSheet);
            } else if (rule.selectorText == selectorText) {
                lastFoundRule = rule;
            }
        });
    };

    $.each(document.styleSheets, function() {
        traverseCSS(this);
    });

    return lastFoundRule;
};

/**
 * スタイルを変更する。
 * 
 * @param {string}
 * selector
 * @param {string}
 * declaration
 */
CSS.setRule = function(selector, declaration) {

    /* {object} */var rule = CSS.getRule(selector);

    if (rule == null) {
        CSS.addRule(selector, declaration);
    } else {
        rule.style.cssText = declaration;
    }
};