SimpleIsm

ハイパフォーマンスCSSの記述方法

CSS

すでにいろいろ出回っていますが、そういった情報は1つだけあれば良いわけではなく、それらの情報をまとめたり、精査していって、さらにより良いものになっていくのがWebの良いところだと思います。業務を行う中でメモしていたものが貯まってきたので、その理由とかを調べてみたのでまとめます。まぁ前置きはこれぐらいにして本題。

CSSをシンプルに効率良く書くということは、ネットワーク転送量の低下、パース速度の向上、そしてHTMLとのマッチング効率化に繋がります。そしてそれは即ち、ページのパフォーマンスを向上させることに繋がります。

また、「効率的なスタイル指定をする」と考えながら臨むと肩に力が入ってしまいますが、「非効率なスタイル指定をしない」という視点で考えていけば、自ずと軽量で効率的なCSSを記述することができると思います。

パターンマッチング(Pattern matching)について

「非効率なスタイル指定」の前にまず、スタイルがあてられる仕組みを知っておくと、理解しやすいです。

CSSではパターンマッチ(Pattern matching)の規則を用いて,文書ツリーの各要素に適用されるスタイル規則を決定します。セレクタと呼ばれるこれらのパターンは単純な要素型名のみのものから,複雑な前後関係(たとえば子供や子孫,兄弟など)を照合できるものまで多岐に亙ります。パターン内に示された全条件がある要素と一致するとき,そのセレクタと要素はマッチ(match)していると謂います。

マッチング回数について

Webブラウザは、読み込んだHTMLをパースして、DOMツリーを作成します。その後、ツリーを辿りながらそれぞれの要素について、各スタイルが合致するかどうかを検証していきます。この作業は基本的には、全ての要素に対して、全てのスタイルルールを検証するという作業になります。つまり、HTML内の全要素数 * CSS内のルール数分、マッチングが行われるということになります。

<head>
   <style type="text/css">
      body { ... }
      div { ... }
      .sample h1 { ... }
   </style>
</head>
<body>
   <div> ... </div>
   <div class="sample">
      <h1> ... </h1>
      <div> ... </div>
   </div>
</body>

上記のような文書があったとします。この文書内には要素が5つ(body, h1, div * 3)、スタイルが3つあります。5つの各要素に対して、3つのルールがマッチするかどうかを検証するので、マッチング回数は5 * 3となり、計15回のマッチングが上記の文書で行われる計算になります。

スタイルシステムによるルールの分類について

スタイルシステムは、キーセレクタ(key selector(ルールの一番右に書かれたセレクタ))により、CSSルールを大きく4つの区分に分けて扱います。

#sample { ... }
.sample p,
address { ... }
div.sample form input[checked="checked"] { ... }

上記の場合、キーセレクタは#sample, p, address, input[checked="checked"]となります。

IDルール(ID Rules)

キーセレクタがID指定してあるもの。以下はその例。

div#sample { ... }
#sample { ... }
table tr.sample td#myCell:active { ... }
クラスルール(Class Rules)

キーセレクタがクラス指定してあるもの。以下はその例。

div.sample { ... }
.menu { ... }
.section .date:active { ... }
タグルール(Tag Rules)

キーセレクタがタグ指定してあるもの。以下はその例。

body { ... }
.section p { ... }
input[type="checkbox"] { ... }
一般ルール(Universal Rules)

上記3つのルールに当てはまらない全てのもの。以下はその例。

* { ... }
.footer * { ... }
:lang(ja) { ... }
[hidden="true"] { ... }

ルールマッチングについて

スタイルシステムは個々のルールにおいて、対象要素にマッチするかどうかを、キーセレクタから順々に(左に向かって)確認していきます。また、スタイルシステムはツリーが対象要素に適合し続けている限り、ルールがマッチするかミスマッチで終了するまで確認し続けます。

具体例
<body>
   <div class="section">
      <div class="menu">
         <ul>
            <li id="aboutMe"> ... </li>
         </ul>
      </div>
      .
      .
      .
   </div>
</body>

上記のHTMLのliに、以下のルールでスタイルをマッチさせてみます。

.section .menu ul li { ... }

キーセレクタであるliがタグなので、このルールは「タグルール」に分類されます。

  1. タグルール中のliにマッチするかどうかを確認し、検証を開始する(5行目にliを発見)
  2. 親要素がulであるかどうかを確認する(4行目にulを発見)
  3. 先祖に"section"というクラス名を持つdivがあるかどうかを確認する(2行目にdiv class="section"を発見)
  4. 全てのルールにマッチしたので、スタイルを適用する

次に、以下のルールでスタイルをマッチさせてみます。

#aboutMe { ... }

これは当然、「IDルール」に分類されます。

  1. "aboutMe"というIDにマッチしたので、スタイルを適用する

以上です。これは極端な例ですが、スタイル指定の効率の良し悪しが想像できると思います。

それでは、以上の「パターンマッチング」を踏まえて、「効率的なCSSを書くための指針」及び「効率的・非効率なスタイル指定」の例を挙げていきたいと思います。

一般ルールを使わない

キーセレクタによって、ルールは4種類に分けられます。「一般ルール」は、IDでもクラスでもタグでもないルールです。このルールは全ての要素が常に立ち止まって確認してしまうため、最も非効率なルールです。Generic Resetなどのように、便利な使い方もあるのですが、なるべく使わないようにしましょう。

IDにタグやクラス名を指定しない

IDはページ内で完全に一意なものであり、それだけで対象要素を特定できるので、そこにタグやクラス名を指定することは全くの無駄です。場合によっては、スクリプトなどでクラス指定を切り替え、それによって動きを変えたい場合などがあるかもしれません。しかしその場合でも、クラス名をユニークにすることで、無駄な記述を避けることができます。

× div#personal_info { ... }
× .footer#copyright { ... }
○ #personal_info { ... }
○ #copyright { ... }

子孫セレクタを使わない

.section * { ... }
blockquote p { ... }
* .menu ul li a { ... }

上記のようなセレクタを子孫セレクタ(descendant selector)と呼びます。子孫セレクタは関係を辿るセレクタとしては、最もマッチングコストが掛かるセレクタです。要素がマッチする限りDOMツリーを辿ることを繰り返すので、キーセレクタがユニバーサルセレクタやタグ指定であった場合、すごいことになります。

要素の関係性で指定するのではなく、クラス指定などを用いて、効率の良いマッチングが行えるようにしましょう。

.menuList ul li { ... }
.categoryList ol li { ... }
.checkList ul li { ... }

例えば上記のようなスタイルの場合、以下のように各li要素にクラス指定した方が、マッチングコストは抑えられます。

.menuList { ... }
.categoryList { ... }
.checkList { ... }

継承に任せる

親要素から継承されるスタイルをきちんと理解して、無駄な指定を省きましょう。

.bookmark ul li { list-style-image: url(bookmarkIcon.gif); }

例えば、list-style-imageスタイルは親要素から継承されます。よって、上記のスタイルは以下のような指定で十分です。

.bookmark { list-style-image: url(bookmarkIcon.gif); }

些細なことのように見えますが、ul要素やli要素が数百あった場合、その数百に対して、「親要素がulで、その先祖が"bookmark"というクラスを持つ要素であるか」という検証を繰り返すことになります。これは恐ろしいほどにマッチングコストが掛かります。検証されるべきなのは、「"bookmark"というクラス名を持つ要素の子要素」だけです。

@importを使わない

Style Sheetはほぼ全ての主要ブラウザで、パラレル処理がサポートされています。これにより、例えば読み込みに1秒掛かるファイルを3つ使う場合でも、3ファイルを並列に読み込むので、トータル1秒で済みます。

しかし@importを使う場合、パラレル処理がサポートされない可能性があります。その場合、3ファイルは順に読み込まれ、トータル3秒掛かってしまいます。

また、記述方法による各ブラウザのサポート状況もマチマチなので、場合によってはスタイルが読み込まれない可能性もあります。

<head>
   <style type="text/css">
      @import url(/css/reset.css);
      @import url(/css/common.css);
      @import url(/css/font.css);
   </style>
</head>

上記のような指定は、以下のようにlink要素で指定しましょう。

<head>
   <link rel="stylesheet" type="text/css" href="/css/reset.css">
   <link rel="stylesheet" type="text/css" href="/css/common.css">
   <link rel="stylesheet" type="text/css" href="/css/font.css">
</head>

ファイルはできるだけ1つのまとめる

Putting a bunch of images into a single image file and selecting them with -moz-image-region performs significantly better than putting each image into its own file.

ばらばらの画像を1つずつ読み込むよりも、1つの画像にまとめて表示領域を指定することで、パフォーマンスはぐっと良くなります。この方法をCSS Spriteと言います。上記の引用文では、-moz-image-regionプロパティを使うように表記されていますが、Mozillaの独自拡張になります。

例えば10個の画像を使用している場合、HTTPリクエストは10ですが、1つにまとめた場合、リクエスト数は1で済みます。1,000バイトの画像を10個読み込むのと、10,000バイトの画像を1つ読み込むのとでは、大分違ってきます。

HTTPリクエストを減らすという観点で言えば、CSSファイルやJSファイルなどもそれに当たります。ファイルはできるだけ1つにまとめましょう。

まとめ

ここで言う「効率的」というのは、あくまで実行時のパフォーマンスにフォーカスしたものです。運用やメンテナンスなどの管理効率とは相反する場合があるかと思います(「子孫セレクタを使わない」など)。また、HTML内に余計なクラス名や、CSSに余計なスタイル指定を追加しないといけなくなる可能性もあります。そうなってしまっては本末転倒です。

しかしながら、ページの高速化は何よりのユーザビリティ向上に繋がります。管理効率やパフォーマンスとのバランスを上手く取りながら、それぞれに合った使用パターンを確立していきましょう。

参考サイト
2009/10/11(Sun) 01:00
<<前の記事
コーディングの宿題
次の記事>>
あの問題について

Category

Archives