uhyohyo.net

JavaScript初級者から中級者になろう

五章第三回 CSSの操作

前回は、CSSがどのような構造をしているのか説明しました。今回は、その構造をいじることについて解説します。前回は、構造の末端にあるCSSStyleDeclarationも解説したので、それを操作するくらいならできるようになったと思います。

CSSRuleの追加と削除

まず、CSSRuleを子(?)に持つCSSStyleSheetやCSSMediaRuleに、CSSRuleそのものを追加したり、あるいは削除したりする方法を解説します。

CSSStyleSheet(やCSSMediaRule)は、insertRuledeleteRuleという2つのメソッドを持っています。その名の通り、insertRuleは新しいCSSRuleを追加、deleteRuleは今あるCSSRuleを除去するメソッドです。

insertRule

まずinsertRuleの使い方から見ていきます。insertRuleは2つの引数を持ち、ひとつめの引数は追加するルール、ふたつめの引数は追加する位置です。これは何番目かを数値で指定するもので、ルールがその位置に追加されます。最初は0番目だから、たとえば0を指定すると一番先頭に追加されるということです。

ひとつめの引数のルールはどのような形で指定するかですが、appendChildなどのときはノードのオブジェクトを作成してそれを引数にしました。今回同じようにCSSRuleを何らかの方法で作って渡すのかというと、実はそうではありません。ここには、追加したいルールをそのまま文字列で指定します。具体的なサンプルを見てみると、

<!doctype html>
<html>
  <head>
    <title>test</title>
    <style type="text/css">
      body {
          background-color: aqua;
      }
      div {
          background-color: white;
      }
      p {
          background-color: yellow;
      }
    </style>
  </head>
  <body>
    <p>testtest</p>

    <script type="text/javascript">
      var stylesheet = document.styleSheets.item(0);

      stylesheet.insertRule("body { background-color:#00ff00; }", stylesheet.cssRules.length);
    </script>
  </body>
</html>

という感じです。insertRuleの第一引数には、

body { background-color:#00ff00; }

という、CSSで書く形がそのまま渡されています。これを渡すと、JavaScript側が解釈してCSSRuleをつくって追加してくれます。今回の場合、CSSStyleRuleが作られたことになりますね。

全体的に確認しておくと、変数stylesheetには、最初の行でCSSStyleSheetが代入されています。また、insertRuleの第二引数はstylesheet.cssRules.lengthが渡されています。cssRulesはそのCSSStyleSheetが持つCSSRuleの一覧で、lengthはその個数ということになります。だから、例えばlengthが3のとき、CSSRuleは0番目から2番目まであるということです。同様にlengthが5なら4番目まで、10なら9番目まであることになります。つまり、(length-1)番目までCSSRuleがあることになります。したがって、length番目に追加すると、それが一番最後に追加されます。この書き方は、一番最後に追加するための書き方だったのです。

さて、実行してみると、新しいCSSRuleが追加されて、新しいのに従って背景が緑に変わりました。これで、ページ全体に自由なスタイルを適用することができます。

また、すでにbodyの背景色を指定する

body {
  background-color: aqua;
}

というのがありますが(これは0番目にあたりますね)、これとの兼ね合いも多少解説します。このように重複する指定があった場合、番号が大きいほうが優先されます。今回の場合、もともとあったのは0番、JavaScriptで追加したのは3番になるから、追加したほうが優先されます。

基本的に新しいスタイルを追加したいときは、一番最後に追加していきましょう。そうすれば、もともとあったもののほうが優先されるということはありません。

ちなみに、試しにinsertRuleの第二引数を0にしてみましょう。そうすると一番最初に追加されるから、もともとあった指定は1番目に移動し、その結果もともとあった方が優先されるため追加してもそのスタイルは反映されません。これらはCSSの、同じ優先度の規則が複数あって重複する場合、あとに書いてあるほうを優先するというルールに従っています。

deleteRule

deleteRuleは、CSSRuleを1つ削除するメソッドです。引数は1つで、insertRuleと同じく数値です。その番号のCSSRuleがなくなります。

<!doctype html>
<html>
  <head>
    <title>test</title>
    <style type="text/css">
      body {
          background-color: aqua;
      }
      div {
          background-color: white;
      }
      p {
          background-color: yellow;
      }
    </style>
  </head>
  <body>
    <p>testtest</p>

    <script type="text/javascript">
      var stylesheet = document.styleSheets.item(0);

      stylesheet.deleteRule(0);
    </script>
  </body>
</html>

このコードでは、0番目、つまり最初の

body {
  background-color: aqua;
}

を削除していることになります。だから、bodyの背景色の指定がなくなって、背景はデフォルトの白(大抵の場合)になります。

CSSStyleSheetの追加と削除

上では、もともとあるCSSStyleSheetに新しいルールなどを追加するものでした。しかし、JavaScriptで新しいスタイルなどを作りたい場合、その文書にもともとCSSStyleSheetが(style要素やlink要素が)存在していない場合がありえます。そこで、CSSStyleSheetそのものを新しく作る必要がでてきます。

実は、DOMではCSSStyleSheetを直接作って追加する方法は提供されていません。そこで、どうするかというと、style要素を新しく作って追加します。style要素やlink要素とCSSStyleSheetは一対一で対応しているから、こうすることで間接的に新しいCSSStyleSheetをつくったことになります。

そしてできた要素ノードのsheetというプロパティが、その要素のCSSStyleSheetを表しています。CSSStyleSheet側からHTMLElementを取得するのがownerNodeプロパティで、その逆をたどるのがsheetプロパティというわけですね。できたCSSStyleSheetは空っぽだから、insertRuleで追加していくことになります。

つまり、例えばp要素の背景を黄色くしたい場合、

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>なんとかなんとか</p>
    <p>なんとかかんとか</p>
    <p>かんとかかんとか</p>

    <script type="text/javascript">
      var newStyle = document.createElement('style');
      newStyle.type = "text/css";

      document.getElementsByTagName('head').item(0).appendChild(newStyle);
      newStyle.sheet.insertRule("p { background-color:yellow; }",0);
    </script>
  </body>
</html>

できたstyle要素のノードのプロパティtypeに設定しているのは、style要素のtype属性に相当するものです。<style type="text/css">の部分ですね。

その次は、document.getElementsByTagName('head').item(0)にappendChildでstyle要素を追加しています。これは、0番目のhead要素ということです。head要素は文書中に1個しかありませんね。これで、正しくstyle要素が追加されました。

それができたら、insertRuleでいよいよ具体的なルールを追加します。今回insertRuleの第二引数が0ですが、もともと空っぽなので大した問題にはなりません。

ちなみに、前回解説したCSSImportRuleで読み込まれたスタイルシートのCSSStyleSheetは、対応するノードが存在しません。その場合はnullです。

削除

CSSStyleSheetを削除するときも、同様に対応する要素を削除します。CSSImportRuleの場合は、そのCSSImportRuleをdeleteRuleで取り除きましょう。

つまり、こんな感じです。

<!doctype html>
<html>
  <head>
    <title>test</title>
    <style type="text/css">
      body {
        background-color: aqua;
      }
    </style>
  </head>
  <body>
    <p>testtest</p>
      <script type="text/javascript">
        var stylesheet = document.styleSheets.item(0);

        var ele = stylesheet.ownerNode;
        ele.parentNode.removeChild(ele);
    </script>
  </body>
</html>

変数stylesheetはひとつあるstyle要素のCSSStyleSheetです。

変数eleに代入しているのはownerNodeです。つまりstyle要素ですね。

その次の行でremoveChildでそのstyle要素を除去しています。removeChildは二章第四回で出てきました。引数で指定した子ノードを除去するというものです。

つまり、つまり、eleを子ノードとして持つノード、つまりeleの親ノードに対してremoveChildを実行する必要があります。eleの親ノードは、つまりele.parentNodeです。

このサンプルを実行してみると、スタイルシートがなくなったことがわかります。