これでできる! クロスブラウザJavaScript入門

第10回 JavaScriptとCSS

この記事を読むのに必要な時間:およそ 7 分

CSSのルールの操作

ここまでに紹介した方法は要素自体を操作してスタイルを変更・取得する方法でした。ここからはCSSのルール自体を操作する方法を解説します。

このルールの操作方法はDOM Level 2 Styleで定義されており,Firefox, Opera, Safari, Chromeなどはこの仕様に沿って実装しています。一方,IE 6~8は独自の仕様で実装しているので処理を分ける必要があります。まずは標準的な方法を見ていきます。

CSSのルールを操作するうえで最初に必要なのはStyleSheetオブジェクトの取得です。ルールの操作はStyleSheetオブジェクトに対して行うので,これがないと始まりません。このStyleSheetオブジェクトは,document.styleSheets から参照できます。このオブジェクトは配列ライクなオブジェクトなので,document.styleSheets[0] で1つ目,document.styleSheets[document.styleSheets.length-1] で最後のStyleSheetオブジェクトを取得できます。

ただし,注意しなければいけないのはこのStyleSheetオブジェクトが外部ドメインのCSSファイルを参照していた場合,ルールに対する操作ができない点です。また,⁠これは事前に回避できる問題ですが)元々ページ内に link[type="text/css"] な要素かstyle要素が存在しない場合,StyleSheetオブジェクトは空の状態です。さらに,どのブラウザも(サードパーティ製などの)ブラウザの機能を拡張する仕組み(以降,拡張機能と呼びます)を持っており,その中にはStyleSheetを追加するものもあります。拡張機能によって追加されたStyleSheetオブジェクトは外部ドメインのStyleSheetと同じくページ側から操作できない可能性があります。

このため,document.styleSheetsからオブジェクトを取得する方法はお勧めできません。その代わりに,StyleSheetオブジェクトを動的に作るとよいでしょう。その方法は簡単で,createElementでstyle要素を作れば,そのsheetプロパティがStyleSheetオブジェクトになっています。

StyleSheetオブジェクトの作成

var style = document.createElement('style');
var head = document.getElementsByTagName('head')[0];
head.appendChild(style);
var sheet = style.sheet;

では,このStyleSheetオブジェクトに対してルールを追加してみます。

ルールの挿入

sheet.insertRule('p{color:red;}', sheet.cssRules.length);

このように,StyleSheetオブジェクトはinsertRuleというメソッドを持っています。このinsertRuleは第一引数にルールを,第二引数にルールを挿入する位置を指定します。第二引数を省略した場合はルールの先頭に追加されてしまいます。セレクタの優先度が同じ場合,後ろにあるルールが適用されるため,最後に挿入されるようにルールの数を取得してinsertRuleに渡しています。

なお,第一引数のルールの部分はセレクタとそのスタイルの組み合わせになっていますが,一度に挿入できるのは1つのスタイル(ルール)のみとなっています。insertRule('p,pre{color:red;}', は可能ですが,insertRule('p{color:red;} pre{color:blue;}', のようにすることはできません。

続いて,IEの独自実装を見ていきます。まず,StyleSheetオブジェクトの作成方法は上記と同様のコードでも動作しますが(ただし,StyleSheetオブジェクトはsheetプロパティではなく,styleSheetプロパティに存在します⁠⁠,IEにはもっと楽な方法が用意されています。

StyleSheetオブジェクトの作成(IE)

var sheet = document.createStyleSheet();

このように,createStyleSheetというまさにStyleSheetオブジェクトを作るためのメソッドが用意されています。こちらは自動的にページに挿入された状態になるので,appendChildなどをする必要がありません。ルールの追加方法もシンプルで,addRuleというメソッドを使用します。

ルールの挿入(IE)

sheet.addRule('p', 'color:red;');

こちらは第一引数でセレクタを,第二引数でスタイルを渡します。第三引数にはinsertRuleと同様に挿入する位置を指定することが可能ですが,省略すれば勝手に最後に追加されるのでほとんどの場合で省略しても問題ありません。

では,クロスブラウザで使えるルール追加関数を定義してみましょう。まず前提として,この関数を使うたびにStyleSheetオブジェクトを作っていたのでは非効率ですから一度だけ作成するようにします(createStyleSheetには31個までしかシートを作成できないという制約もあります⁠⁠。ただし,関数を一度も使わないのにStyleSheetオブジェクトを作成するというのも避けるようにします。つまり,一度目に呼び出された時にStyleSheetオブジェクトを作り,次回からはそのオブジェクトを使いまわすようにします。なお,今回は(非標準ですが)createStyleSheetが使える場合はそちらを使用することにします。

ルール追加関数

function addRule(selector, style){
  var sheet;
  if(!addRule.add){
    if (document.createStyleSheet){
      sheet = document.createStyleSheet();
      addRule.add = function(selector, style){
        return sheet.addRule(selector, style);
      };
    } else {
      var style = document.createElement('style');
      var head = document.getElementsByTagName('head')[0];
      head.appendChild(style);
      sheet = style.sheet;
      addRule.add = function(selector, style){
        return sheet.insertRule(selector+'{'+style+'}', sheet.cssRules.length);
      };
    }
    // 追加したスタイルを無効化する関数
    addRule.disable = function(){
      return sheet.disabled = true;
    };
    addRule.enable = function(){
      return sheet.disabled = false;
    };
  }
  // addRuleが引数ありで呼び出されたかチェック
  if (arguments.length){
    return addRule.add(selector, style);
  } else {
    return addRule;
  }
}

初回呼び出し時にaddRule関数のプロパティとしてadd, disable, enableというメソッドを追加しています。なお,arguments.lengthで引数の数をチェックして,引数なしで呼び出されたときは各メソッドの定義だけをするようにしました。つまり次のように使うことも可能です。

ルール追加方法

addRule().add('p','font-size:120%;');
addRule('p','color:black;');
addRule.add('p','font-weight:bold;');
addRule.disable();// 上記のスタイルを無効化

ちなみに,Opera,Safari,ChromeなどはIEの独自拡張であるaddRuleもサポートしているので,そちらを使用しても問題はありません。

さらにおまけで,DOM Level 2ではcreateCSSStyleSheetというメソッドが定義されています。実際に,Safari, Chromeはdocument.implementation.createCSSStyleSheetというメソッドが定義されています。しかし,このcreateCSSStyleSheetで作ったStyleSheetオブジェクトはページのdocumentに関連付けられておらず,関連付ける方法もないので実用はできません。

CSSの動的な生成

最後に,スタイルシート自体を動的に作る方法を紹介します。やはりIEの場合はcreateStyleSheetで,それ以外はstyle要素を作ります。

クラス名の操作

<div class="css-sample4">
  <div>サンプル1</div>
  <div>サンプル2</div>
  <div>サンプル3</div>
</div>
<script>
(function(){
  var btn  = document.getElementById('css-sample4-btn');
  btn.onclick = function (){
    var css = '.css-sample4 div{'+
    '  font-size:20px;'+
    '  color:white;'+
    '  background-color:#444;'+
    '  width:100px;'+
    '  padding:10px;'+
    '  margin:5px;'+
    '}';
    if(document.createStyleSheet){
      var sheet = document.createStyleSheet();
      sheet.cssText = css;
    } else {
      var style = document.createElement('style');
      style.textContent = css;
      var head = document.getElementsByTagName('head')[0];
      head.appendChild(style);
    }
  };
})();
</script>
サンプル1
サンプル2
サンプル3

上記の通り,CSSをJavaScript内に文字列リテラルで書くのはおすすめできませんし,わざわざJavaScriptからスタイルを作らなければいけないケースというのはそうそうないので,あまり実用的ではないかもしれません。

まとめ

今回はCSSをJavaScriptから操作する方法を紹介しました。DOM Level 2に相当する部分のため,IEとそれ以外で差があるため少々面倒なことになっています。ただ,IE 9ではやはりほかのブラウザと同様の方法で実装できるようになっています。

次回はいよいよXMLHttpRequestやJSONPなどの非同期処理を取り上げる予定です。

著者プロフィール

太田昌吾(おおたしょうご,ハンドルネーム:os0x)

1983年生まれ。JavaScriptをメインに,HTML/CSSにFlashなどのクライアントサイドを得意とするウェブエンジニア。2009年12月より、Google Chrome ExtensionsのAPI Expertとして活動を開始。

URLhttp://d.hatena.ne.jp/os0x/