2014/12/11 03:29:17

javascriptでスタイルシートを自在に操る(styleSheetsオブジェクト)入門基本編

目次(クリックするとジャンプします)
  • 1:javascriptでスタイルシートに到達する
  • 1.1:スタイルシートの種類
  • 1.2:スタイルシートを取得する
  • 1.3:スタイルシートの総数を得る
  • 2:スタイルシートの中身に到達する
  • 2.1:cssルール
  • 2.2:cssルールに到達する
  • 2.3:cssルールを取得する際の注意点
  • 2.4:cssルールの総数を取得する
  • 2.5:セレクター名を取得する
  • 3:スタイルシート自体を追加する
  • 3.1:スタイルシートの追加
  • 4:cssルールの追加・削除を行う
  • 4.1:cssルールの追加
  • 4.2:cssルールを削除する
  • 5:プロパティの追加・削除・変更を行う
  • 5.1:プロパティのについて
  • 5.2:プロパティの追加・変更
  • 5.3:プロパティを削除する
  • 5.4:プロパティ名を取得する
  • 5.5:設定されているプロパティの総数を取得する
  • 5.6:プロパティの値の取得
  • 5.7:プロパティの値の加工parseInt関数とparseFloat関数の活用
  • 6:まとめ

スタイルシートに到達するには生javascriptを使用する必要があります(もしかしたらライブラリがあるのかもしれないが)。あまり需要のない事柄なのかこの手の日本語情報はwebでもあまり見つからない気がします。

jQuery全盛の時代スタイルシートを書き換える必要などどこにあるのか。@MINOにはあまりわからないです。

一つ懸念なのはstyle属性でのcss指定は「デザインを分離する」理念に反するのではないかと思うのです。SEO的にはどうなんでしょうか?

そこらへんの疑問もアリながらですが、生javascriptでゴリゴリするのも知識がついていいのではないかと思います。

javascriptでスタイルシートに到達する

スタイルシートの種類

スタイルシートは大きくわけて2つあります。一つは<style></style>で囲まれた以下の様なインラインスタイルシートです。

//インラインスタイルシートの例
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>サイトのタイトル</title>
<style>
body{
    font-family:
    "Lucida Grande", "segoe UI",
    "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", 
    Meiryo, Verdana, Arial, sans-serif;
    word-break: normal;
    word-wrap: break-word;
    line-height:
    float: left;
}
</style>
</head>
<body>
・・・・

もう一つはおなじみだと思いますが、スタイルシートファイルで指定されるスタイルシートです。

/*スタイルシートファイル*/
body{
    font-family:
    "Lucida Grande", "segoe UI",
    "ヒラギノ角ゴ ProN W3", "Hiragino Kaku Gothic ProN", 
    Meiryo, Verdana, Arial, sans-serif;
    word-break: normal;
    word-wrap: break-word;
    line-height:
    float: left;
}

以下のようなstyle属性はスタイルシートとしてはカウントされないです。

//style属性
<body style="width:980px;">

スタイルシートを取得する

スタイルシート(オブジェクト)を取得するには以下の様に記述します。

//スタイルシート(オブジェクト)を取得する
var stylesheets = document.styleSheets.item( index );

引数にindexとありますが、ここにはスタイルシートの番号が入ます。 スタイルシートは単体の場合もあるし、サイトデザインによって複数のファイルを使用する場合もあるでしょう。wordpressなどの場合プラグイン用のスタイルシートが読み込まれる場合もあります。複数のスタイルシートが存在することになる為、スタイルシートを特定する必要があります。

スタイルシートの番号に関しては、通常、指定された順になります。インラインスタイルシートもスタイルシートファイルも<heae>内で指定することになりますが、その指定順がそのまま番号になると考えてもらえればと思います。

index0から始まるので、一番目のスタイルシートを特定したい場合は0を指定します。

//最初のスタイルシートを取得する
var stylesheets = document.styleSheets.item( 0 );

スタイルシートの総数を得る

複数のスタイルシートが存在し、その数が既知ではない場合や、他のサイトでもロジックを使いたい場合など、スタイルシートの総数が判らないと不便になってしまいます。スタイルシートの総数を知るには以下の様に記述します。

//スタイルシートの総数を取得する
var stylesheets_number = document.styleSheets.length;

styleSheetsオブジェクトlengthメソッドを使用して総数を得ることができます。

このメソッドで得られる数値はから始まっているので注意してほしいです。もしこの数字をループ等でdocument.styleSheets.itemの引数に使うと、順番がずれてしまうことになり兼ねないです。

スタイルシートの中身に到達する

cssルール

スタイルシートにはセレクターとそのセレクターに対してのcss指定が記述されているかと思います。これを便宜上cssルールと呼びたいです。たとえば以下はcssルールが3つあるということになります。

/* cssルールが3つ */
#main{
width: 100px;
}
.class{
width: 50px;
}
.class div{
width: 20px;
}

cssルールに到達する

その一つ一つを取得する為には以下の様に記述します。

//cssルールを取得する
var cssRule = document.styleSheets.item( index ).cssRules.item( index );

indexは前述と同じように順番となります。スタイルシート何番目のcssルールの何番目といった指定の方法になります。

たとえばスタイルシート2番目のcssルール3番目を取得したい場合は以下の様に記述します。

//cssルールを取得する
var cssRule = document.styleSheets.item( 1 ).cssRules.item( 2 );

index0から始まることに注意。

cssルールを取得する際の注意点

上記ではcssRulesを使用したが、IE8以前ではcssRulesは実装されておらず、代わりにrulesを使用する必要があります。@MINOが調べたところではchromeはcssRulesでもrulesでもオブジェクトを取得できた。IE9以上ではcssRulesでも取得できるようです。

現在ではIE8以下の対応の必要性はなくなっていくと思うのであまり気にすることではないかもしれないですが、それでもブラウザシェアから見るとIE8以下もあながち無視できないです。(2014/12現在サポート切れているIE6でのアクセスがそれなりにあるのが@MINOには理解できない)

その為古いIEの為にクロスブラウザ対応しておくこともできます。 以下に具体例を示したいです。

window.onload= function(){
    //長ったらしいので変数に代入
    var css = windows.document.styleSheets.item( 0 );
    //論理演算子で存在する方を代入(参照) 
    var rules = css.cssRules || css.rules;
    //CSSルールの数を調べる 
    var rules_length = rules.length;
    //CSSルールの数だけループしてCSSの内容をコンソールに表示する
    for(var i = 0 ; i < rules_length ; i++ ){
        console.log( rules.item( i ).cssText );
    }
};

値が定義されていない場合やプロパティが存在しない場合、その変数なりプロパティなりを参照するとundefinedが返ってくることになります。つまりcssRulesが無ければcss.cssRulesundefinedなり、rulesがなければcss.rulesundefinedになります。

普通に書くとどちらか一方しか使えない訳でブラウザの差を吸収できないです。その為に論理演算を使おうということなのです。

式というと1+2のようなものを思い浮かべるかもしれないですが、論理演算子は真か偽を計算する演算子。

javascriptの仕様ではundefinedfalseの評価となります。その他にもnull、0、空文字列("")falseの評価となる奴らです。

つまりnull、0、空文字列("")が入っている式はfalseになるということが言えます。 逆になんであれnull、0、空文字列("")の値以外が入っていれば今回の論理演算の場合tureの評価となります。

今回の場合、評価の結果、定義されていて値の入っている式が代入されるという具合です。

仮にcssRulesが無かった場合はcssRules→undefined→falseとなり、css.cssRules は却下され、css.rulesが代入されるということになります。

ブラウザの差を吸収するのに使えるテクニックなので参考にしてもらえたらと思います。

cssルールの総数を取得する

ループ処理などを行う場合、cssルールの総数が判っていないと不便です。cssルールの総数を取得するには以下の様に記述します。

//cssルールの総数を取得する
var cssRule_number = document.styleSheets.item( index ).cssRules.length;

styleSheetsオブジェクトの時と同じようにlengthメソッドでcssルールの総数を取得できます。

セレクター名を取得する

さらにcssの中身に迫っていきたいと思います。今度はセレクター名を取得する方法です。書き換えたいcssルールをセレクターで特定するときなどに必要になるでしょう。以下の様に記述します。

//セレクター名を取得する
var selectorname = document.styleSheets.item( index ).cssRules.item( index ).selectorText;

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例 */
#main{
width: 100px;
}
.class{
width: 50px;
}
.class div{
width: 20px;
}

以下の記述でセレクター名を取得すると”#main”が取得できるはずです。

//セレクター名を取得する
var selectorname = document.styleSheets.item( 0 ).cssRules.item( 0 ).selectorText;

スタイルシート自体を追加する

スタイルシートの追加

新規でcssルールを追加する場合、余計な混乱を避ける為、新たなスタイルシートの土台を作り、そこに追加していくのも手だと思います。

またブラウザのアドオンやプラグインによって読み込まれるスタイルシート数が異なる可能性もあります。IEで表示したときの3番目のスタイルシートが、chromeで必ずしも3番目になるかどうかはわからないです。

変更を加えたいスタイルシートが必ず末尾に読み込まれるように工夫するなどが必要になるかもしれないです。セーフな方法として、スタイルシート新規追加が重要になってくる可能性もあります。スタイルシート自体の追加を覚えておいて損はないかと思います。

直接スタイルシートを追加するメソッドは無いです。そのかわり<style></style><head></head>内に挿入してスタイルシートの土台としてしまう手があります。

以下の様にして<style></style>を挿入することができます。

//&lt;style&gt;&lt;/style&amp;gtを挿入する
var tag = document.createElement( 'style' ); 
tag.type = "text/css"; 
document.getElementsByTagName( 'head' ).item( 0 ).appendChild( tag );

createElementstyle要素を作り、headタグ内appendChildで作ったstyle要素を挿入しています。headタグは通常複数存在しないので最初のheadということで0指定固定で構わないはずです。

作ったスタイルシートには

document.styleSheets.item( document.styleSheets.length - 1 );

でアクセスすることが可能です。

もちろん新規に作ったスタイルシートは中身が空っぽなので後述するcssルールの追加で内容を作っていくことになります。

cssルールの追加・削除を行う

cssルールの追加

まずはcssルールの追加の方法。cssルールとは前述したように「セレクターに対してのcss指定のひとかたまり」のことです。

以下の様な記述で追加をすることが可能です。insertRuleというそのままのメソッドが存在するのでそれを使用して追加を行う。

//cssルールの追加
document.styleSheets.item( index ).insertRule( cssrule , insertindex );

cssruleの部分はcss指定をする時のように

".class{ width: 100px; }"

をそのまま指定する形となります。

insertindexは挿入位置の指定です。cssは同じセレクター、プロパティが存在した場合、後から指定した方が有効となるように出来ています。その為、場合によっては挿入位置も重要な要素になってきます。 0から数え始めるので注意が必要。

以下のようなcssルールを先頭に追加したい場合を想定すると、

//追加したいcssルール
.class{
width: 100px;
}

以下の記述が具体例となります。(オブジェクト指定が長くなってくるので適時変数に格納することをお勧めする)

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.insertRule( ".class{ width: 100px; }" , 0 );

末尾に加えたい場合は以下の様に記述すると良いです。

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.insertRule( ".class{ width: 100px; }" , stylesheet.cssRules.length );

stylesheet.cssRules.length1から始まるので、丁度最後のcssルールの次(つまり空白位置)を指すことになります。通常は最後尾に追加することで、問答無用でcssが有効になるし、途中に挿入しなければならない積極的な理由でもない限りデフォルトでこのような記述をしてもいいのではないかと思います。

cssルールを削除する

既存のcssルールを削除したい場合もあると思います。そんな場合は以下の様に記述します。 これもまんまなdeleteRuleというメソッドが用意されているので使わせてもらいましょう。

//cssルールを挿入する
stylesheet = document.styleSheets.item( index );
stylesheet.deleteRule( deleteindex );

deleteindexは削除するcssルールの番号(位置)になります。0が先頭になるので、例えば2番目のcssルールを削除したい場合は以下の様な記述になります。

//cssルールを挿入する
stylesheet = document.styleSheets.item( 0 );
stylesheet.deleteRule( 1 );

これで2番目のcssルールが削除されるはずです。

プロパティの追加・削除・変更を行う

プロパティのについて

最初に断わっておきたいが、下記はオブジェクトです。オブジェクトに言及すると難しくなりかねないので(むしろ@MINOがよくわかっていないので)言及するのを避けてきたがプロパティでは少し説明しておきたいです。

//オブジェクト
document.styleSheets.item( 0 ).cssRules.item( 0 );

このオブジェクトは実装されているすべてのプロパティをすでに持っている状態です。試しにご自分のサイト等で中をのぞいてみてほしいです。

//cssRulesオブジェクトの中身を見てみる
//※styleSheets.item、cssRules.itemの番号は各位の環境に合わせて存在する番号で
console.log(document.styleSheets.item( 0 ).cssRules.item( 0 ));

コンソールに指定したcssRulesの中身が表示されるかと思います。下の画像はChromeで上記のコードを実行した際のコンソール内容をキャプチャしたものです。

image3004

style直下に大量のプロパティが並んでいるかと思います。(これは裏を返せばブラウザが実装しているプロパティを確認できるということでもあります。余談だがベンダープレフィックス付きで実装されているプロパティも確認することができます。 )

そのため本質的には「プロパティを追加・削除する」というより、「プロパティに値をセットする・初期化する」と言ったほうが正しいのかもしれないです。

見かけ上のプロパティ文は以下のコードで確認することが可能です。

//プロパティ文
document.styleSheets.item( 0 ).cssRules.item( 0 ).cssText;

追加削除を行った場合、見かけ上ではこのcssTextに変化が起こる(他にも変化は起こるが)。値をセットしていないプロパティに値をセットすればそれは追加ということになり、プロパティ文が追加されることになります。

後述する削除方法で見かけのプロパティ文は確かに削除されるが、オブジェクトプロパティが削除されるわけではなく、値が初期化されるというイメージに近いことを頭の片隅に入れてもらえればと思います。

プロパティの追加・変更

追加の場合は見かけ上プロパティ文が追加されるが、前述したように本質的には値がセットされていませんでしましました。プロパティに値をセットすること、変更はプロパティの値を書き換えるということになります。

その為、追加と変更にはメソッド的な違いが必要ないです。プロパティに値をセットするメソッドが存在していればいいからです。

以下の記述で追加・変更ができます。

//追加・変更する
document.styleSheets.item( index ).cssRules.item( index ).style.setProperty( property , value );

スタイルシート、cssルールを特定した上で、プロパティ(property)値(value)を指定する形です。具体的に例を示したいです。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.classheightの設定値が"2000px"から"500px"に書き換えられることになります。

//追加・変更
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.setProperty( 'height' , '500px' );

以下の様に存在しないプロパティを追加した場合、見かけ上cssText"margin: 5%;"が追加されることになり、オブジェクトプロパティのmargin5%が設定されることになります。

//追加・変更
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.setProperty( 'margin' , '5%' );

プロパティを削除する

何度もしつこいようですが、本質的には値の初期化ということになります。見かけ上はcssTextからは削除されます。

以下の記述で削除ができます。

//削除
document.styleSheets.item( index ).cssRules.item( index ).style.removeProperty( property );

削除の場合はプロパティ名(property)の設定のみです。具体的に例を示したいです。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.classheightが削除されることになります。

//削除
document.styleSheets.item( 0 ).cssRules.item( 1 ).style.removeProperty( 'height' );

プロパティ名を取得する

プロパティ名を取得してみよう。しかしここで取得できるのは見かけ上プロパティ文として記述されている各プロパティの名前とは若干違う。

例えば下記のようにmarginを設定した時、

/* cssルール例 */
#main{
margin: 0px;
width: 100px;
}

実際には"margin-top"、"margin-right"、"margin-bottom"、"margin-left"がプロパティ名として現れることになります。marginだけでなくpaddingborderなどのように、細分化された階層があるプロパティは、その細分化された各プロパティ名がすべて現れます。

その為後述しますが、プロパティ総数が思っていたより多いということが起きるのです。この話は後述の「設定されているプロパティの総数を取得する」で説明したいです。

ちょっとややこしいが、styleオブジェクト上に現れるプロパティ名は値であり、オブジェクトプロパティのことではないです。前述したオブジェクトはプロパティをすべて持っているというのはオブジェクトプロパティとしての話です。以下の画像を見てもらえるとイメージがつかめるかと思います。

image3132

とりあえずプロパティ名を得るには以下の様に記述します。

//プロパティ名を取得する
var property_name = document.styleSheets.item( index ).cssRules.item( index ).style.item( index );

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例 */
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述でプロパティ名を取得するすると"height"が取得できるはずです。

//プロパティ名を取得する
var property_name = document.styleSheets.item( 0 ).cssRules.item( 1 ).style.item( 1 );

細分化された階層がないプロパティなら素直に取得できるが、この方法でのプロパティ名取得はあまりお勧めできないです。オブジェクトプロパティに対してのロジック(例えば値がセットされているオブジェクトプロパティを取得し、目的のプロパティに到達するなど)がよいのではないかと思います。

設定されているプロパティの総数を取得する

セレクターに紐づいているプロパティの総数を取得するには以下の記述となります。

//プロパティ名を取得する
var property_name = document.styleSheets.item( 0 ).cssRules.item( 1 ).style.length;

ここでもlengthメソッドを使って取得することになります。

プロパティ総数を得ることができれば、ループ回数を特定できるので設定されているプロパティ名をすべて取得するロジックも組むことができます。

しかし前述したように、marginなど細分化された階層があるプロパティは、細分化されたプロパティがすべてカウントされることになります。そのため、marginしか設定していない場合でも、"margin-top"、"margin-right"、"margin-bottom"、"margin-left"が現れる為、length4となります。しかもmarginは現れないです。

styleオブジェクトにはちゃんとオブジェクトプロパティとしてmarginが存在していますので、オブジェクトプロパティ名の取得の方が実用性が高いかもしれないです。

前述した方法でのプロパティ名の取得とプロパティの総数の取得は十分注意して行う必要があります。

プロパティの値の取得

すでに設定されている値を取得したい場合があるかもしれないです。既存の値を加工して用いたい場面はよくあることです。プロパティの値の取得に関してもメソッドが用意されています。以下の様に記述します。

//値の取得
var property_value = document.styleSheets.item( index ).cssRules.item( index ).style.getPropertyValue( property );

プロパティ名(property)を指定して値を取得する形です。

具体例を示したいです。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 100px;
}
.class{
width: 50px;
height: 2000px;
}
.class div{
width: 20px;
}

以下の記述で.class divwidthの値である20pxが取得されることになります。

//値の取得
var property_value = document.styleSheets.item( 0 ).cssRules.item( 2 ).style.getPropertyValue( 'width' );

プロパティの値の加工parseInt関数とparseFloat関数の活用

getPropertyValueで取得する値は文字列です。計算をするためには数値にする必要があります。cssの値は多くの場合px%emなどの単位が付いている事が多いです。文字列操作で除去しようとすると条件付けが大変になってしまいます。

javascriptでは型変換用の便利な関数が用意されています。

parseInt関数parseFloat関数です。

これらの関数は文字列を数値へと変換してくれます。 といってもどんな文字列でも良い訳ではないです。ちゃんと数字に変換できる根拠をもっている文字列が対象になります。

たとえば20px96%などは数字部分が存在するので変換が可能です。他にもいろいろ条件はあるのですが、cssプロパティの値で数字を持つ文字列ならほぼこれらの関数で数値に変換できます。

働きの違いですが、parseInt関数は整数に変換parseFloat関数は浮動小数に変換することができます。 以下のコードを試してみてほしいです。

//関数の働きの違い
console.log(parseInt("20.66px",10));
//20と表示されるはず
console.log(parseFloat("20.66px",10));
//20.66と表示されるはず

昨今はレスポンシブデザインの影響で小数点値が用いられることが多いので、parseFloatを使った方がトラブルがないのではないかと思います。

第二引数の10は基数の指定です。おそらく10進数を使うことになると思うので10と指定しておけば問題はないです。8なら8進数だし、16なら16進数になります。省くこともできるが、デフォルトがブラウザによって異なるようで、きちんと指定したほうが問題が起きなくていいかと思います。

実際にgetPropertyValueで取得した値を加工する具体例を示したいです。

以下の様なスタイルシートがあった場合(スタイルシートとしては0番目)

/* cssルール例*/
#main{
width: 33.2558%;
}
.class{
width: 23.5698%;
}
.class div{
width: 9.22%;
}

以下の記述で.classwidthの値である文字列"23.5698%"が変換され数値23.5698を得ることができます。

//値の取得
var parse_property_value = parseFloat(document.styleSheets.item( 0 ).cssRules.item( 1 ).style.getPropertyValue( 'width' ));

まとめ

かなり長い記事になってしまったが、基本的な事柄は網羅できたのではないかと思います。動作確認しながら記述したが、間違っていたらごめんなさいです。

スタイルシートですが、書き換えた瞬間(体感的には)に適用されるようです。ウインドウサイズの変更によるレスポンシブロジックテストをjQueryではなく(style要素ではなく)、スタイルシート書き換えで行ってみたが、画面サイズが変わると組み上げたロジックがきちんと追従してきていましましました。(chromeとIE11でテスト)

少なくとも現行のブラウザではスタイルシート書き換えをレスポンシブデザインに用いることは可能なのないでしょうか。

しかしながらスタイルシート書き換えの弊害(たとえばメモリを食うとか、動作が遅くなるとか)も良く分かっていないです。思いつくのは記事内でも上げたようにスタイルシート数の相違とクロスブラウザ対応くらいです。

逆にstyle要素でのcss付与の弊害もあんまり思いつかないです。世は猫も杓子もjQuery。style要素でのcss付与はありふれており、もしかしたら弊害などないのかもしれないです。

jQueryがなぜスタイルシート書き換えではなくstyle要素によるcss付与にしたのか調べたかったのですが、いかんせん英語ができないのでよくわからなかった。ここら辺の理由が判ればもっと胸をはってスタイルシートオブジェクトの活用をうたえるだろうか。

9割方無職の@MINOが気にすることではないのかもしれないです。

いずれにしてもDOMの内部を知ることは良いことに思えるので、こんどは応用編も記述したいと思っています。