IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

CSS の「inherit」で継承されるのは Computed value 以外にありえない(と思ってる)

ちょっと色々調べていて

以下のような記事を読みました。
line-height の値には単位なしが良いとされる理由 - lucky bag
本当に役立つ素晴らしい記事だと思います。

でも、一カ所だけ確認したい箇所がありました

僕が間違っているかもしれないので、はっきりさせたいのです><
本当に細かい箇所なので、自転車置き場の議論です><
ごめんなさい。

で、その箇所

一方、単位なしで指定すると、line-height 値には同じく font-size と掛け合わせた計算値 16px が指定される。そして、子孫要素である em にはこの計算値ではなく指定値である 1 が継承され、em の font-size と掛け合わせた数値 24px が行ボックスの高さになる。

line-height の値には単位なしが良いとされる理由 - lucky bag

この言い回しだと、計算値(Computed value)ではなく、例外的に指定値(Specified value)が継承されるように読めますが、
以下の仕様から
Visual formatting model details
Computed value が Specified value と同じ値になるだけで、継承されるのはやはり Computed value だと僕は思っています。
僕はバリバリの CSS 書きではないので、この辺、ちょっと自信がないのですが、
CSS の「inherit」で継承されるのは Computed value 以外にありえないですよね?
これを自分の中ではっきりさせたいので、誰か教えてください><
自分の調べたかぎりは正しいと思っています。

(追記)そっか

あ、これって CSS 2 と CSS 2.1 の違いなのか!
CSS 2 は Computed value と Used value が分かれていないのか!
解決しました><
ごめんなさいごめんなさい><

CSS の「値」とは何か

この前

以下のようなエントリを書きました
CSS の名前の整理 - IT戦記

今回は CSS における

この「value(値)」という言葉の意味、そして曖昧さについて書きたいと思います。

6 つの値

CSS において「値」は曖昧な言葉です。
なので、「値」という言葉を使って CSS の説明をするのは非常に効率が悪かったりします。
たとえば、「ここの値って、 hogehoge の意味の値?」「いやいや、ここの fugafuga の意味の値が piyopiyo なんだよ」というように、まったく無駄な会話が繰り広げられるわけです。
ですので、今日からはちゃんと CSS の「値」をちゃんと説明できるように、「値」に以下の 5 つの名前を付けましょう。

  1. Declared values
  2. Cascaded values
  3. Specified values
  4. Computed values
  5. Used values
  6. Actual values
何故、 6 つ?

これらの「値」たちは要素がレンダリングされる過程で計算された「途中の値」を表しています。
まず、 Declared values から Cascaded values が求められ、 Specified values から Completed values が求められ、、、最終的には要素がレンダリングされる。という感じです。
そして、この値が計算されていく過程はすべての要素に対して一斉に行われる訳ではなくて、 Computed values までは親要素から順に計算されていきます。それは、子要素の Specified values のの計算に親要素の Computed values が必要だからです。
それでは順に、値の意味、遷移を見ていきましょう

Declared values

この「Declared values」という言葉は CSS の仕様に書かれているわけではありませんが、以下に続く 5 つの「値」と区別するために僕が付けた名前です。
「Declared values」とは、 CSS を書いた人が実際に書いた値のことです。
一つの要素の一つのプロパティに対して、複数の「値」が宣言されている場合があります。たとえば <div class="hoge"> に対して div { width: 10px } .hoge { width: 20px } というルールセットが宣言された場合です。
ブラウザが CSS をどう解釈したかではなく、CSS を書いた人が実際に書いた値に対して言及したいときにこの言葉を使うといいです。

Cascaded values

「Cascaeded values」とは、前回説明したカスケーディング(一つの要素の一つのプロパティに対して、複数の「値」が宣言されている場合に一つの値を決めるしくみ)で勝った「値」を表します。
つまり、カスケーディングによって「Declared values」から一つ選ばれた「値」が「Cascated values」ということになります。

ちなみに、カスケーディングする前にショートハンドプロパティの値はそれぞれの値に分解されます。
ショートハンドプロパティとは「margin」や「padding」などのいくつかの宣言がまとまったプロパティのことで、ショートハンドプロパティじゃないプロパティに分解することが出来ます。
たとえば、「margin: 10px」という宣言は「margin-left: 10px」「margin-right: 10px」「margin-top: 10px」「margin-bottom: 10px」という風に分解されます。
ですので、実際はショートハンドプロパティの「Cascaded value」はないです。
でも、 margin-(left|right|top|bottom) のすべてのプロパティの「Cascaded values」に言及する場合に「margin プロパティの Cascaded values」とか表現することはありますね。

Specified values

「Specified values」とは、「継承」または「初期値の割当」が行われた直後の「値」のことを言います。
「継承」というのは、 inherit という「値」をもったプロパティが、親要素の「Computed values」をコピーすることを言います。
「初期値の割当」というのは、 initial という「値」をもったプロパティが、「初期値」になることを言います。「初期値」は、プロパティごとに決まっています。
また、ルート要素(HTML 要素)が「継承」を行おうとした場合は、「初期値の割当」が行われます。
「値」を持たないプロパティは「継承」か「初期値の割当」が自動で行われます。どちらが行われるかは、プロパティごとに決まっています。
この辺は、またややこしいので別のエントリにまとめたいと思います。

Computed values

「Computed values」とは、「レンダリングに依存しない値」に対して単位の変換を行った後の「Specified values」のことを言います。
たとえば、 HTML 要素の width などは実際にブラウザの横幅が分かるまで(レンダリングするまで)計算することできませんよね。ここでは、そういう「レンダリングしないと分からない値」以外の「値」のことを「レンダリングに依存しない値」と言います。
で、実際にどういう風に単位変換されるかというと、

  • em, ex → px
  • レンダリングに依存しない % 値 → px
  • 相対URL → 絶対URL
  • bolder → er が取れた形で

などなどです。
また、ここで重要なのは「継承」に使われるのはこの値だということです。たとえば、 width が 200% の場合、その値がこの段階で求められるかどうかで、継承される値は 2 倍変わってきます。ですので、 width, height の「継承」はしないほうがいいと思います。(明示的に inherit と書かない限りは継承されないので、実害はないと思いますが)
あと、この「値」までは、レンダリングされなくても求めることが出来るため。極端な話、 HTML と CSS が与えられれば暗算することも可能なはずです。
ここら辺もいろいろあるので、そのうちまとめたいと思います。

Used values

「Used values」とは、「レンダリングに依存する値」に対して単位の変換を行った後の「Computed values」の値のことを言います。
レンダリングに依存する値」は具体的にいうと、(祖先要素も含めて)%, auto 指定された widthheight の「値」のことです。
%, auto は px に単位変換されます。
ちなみに、 JavaScript の getComputedStyle で取得できる CSSStyleDeclaration オブジェクトはこの「値」を持っています。

Actual values

「Acltual values」とは、実際にレンダリングされた「値」のことを言います。
たとえば、「Used value」が 4.2 px でも実際ディスプレイにはは 4 px しかレンダリングされません。
ですので、この 4 px に言及したい場合にこの言葉を使います。

という訳で

CSS の「値」を求める過程と、その過程での「値」を振り返ってみました。
このイメージをしっかり持つことは、 CSS を書く場合に非常に重要です。

また

JavaScript で CSSStyle* 系のオブジェクト をいじる場合は

  • どの 要素 or ルールセット の
  • どの プロパティ
  • どの 単位 で

書き換えれば、無理のないレンダリングが出来るかを考えるのが非常に重要です。
たとえば、ここをいじったせいでこっちもいじらければならない><
というようなことを無くすことができ、パフォーマンスを何倍にも向上させることができます(マジで
この知識はそういう場面でも威力を発揮します。

まとめ

もう、 CSS の値については完璧ですね!
この説明、自分で書いててめっちゃ難しかったです><

CSS の名前の整理

CSS の話をする時に

名前をはっきりさせてないと混乱するので、整理しておこう。自分用です。けっこう適当です。

仕様

ここの仕様を見て書いています。
CSS Syntax Module Level 3

CSS

日本語では「スィーエスエス」と言いますね。
CSS とはHTML, XML の要素をどのように表示するかを示すための仕様のことです。

style sheet

日本語では「スタイルシート」と言いますね。
これはすごくやっかいな言葉です。以下の二つ意味があるからです。

rule

日本語では「ルール」とか「規則」とか言いますね。
CSS スタイルシート内に書かれている @import url("hoge.css"); や @media print { ... } や div { width: 100px; height: 100px } のことです。
JavaScript では CSSRule オブジェクト

rule set

日本語では「ルールセット」とか「規則集合」とか言いますね。
ルールの中でも div { ... } とかのようなものです。 @ が付かないルールと覚えておけばいいと思います。
JavaScript では CSSStyleRule オブジェクト

at-rule

日本語では「アットマークルール」と言いますね。
ルールの中でもルールセット以外のものです。 @import ... とか @media ... とか先頭に @ が付くルールですね。
JavaScript では、以下のようにそれぞれのアットマークルールごとにオブジェクトが定義されています。

  • CSSCharsetRule
  • CSSFontFaceRule
  • CSSImportRule
  • CSSMediaRule
  • CSSPageRule

selector

日本語では「セレクタ」と言いますね。
ルールセットの中括弧の前に書かれてるやつです。 body, div { ... } とかだったら body, div がセレクタですね。
JavaScript だと CSSStyleRule オブジェクトの selectorText プロパティの値のこと。

declaration

日本語では「宣言」と言いますね。
ルールセットの中括弧の中にセミコロン区切りで書かれているやつです。 body, div { width: 100px; height: 100px } だと width: 100px と height: 100px が宣言ですね。
JavaScript だと CSSStyleDeclaration オブジェクトのそれぞれのプロパティ名とプロパティ値のこと

property

日本語では「プロパティ」と言いますね。
宣言のコロンで区切られた左側のやつです。 width とか font-size とかですね。
JavaScript だと CSSStyleDeclaration オブジェクトプロパティ名のこと

value

日本語では「値」と言いますね。
宣言のコロンで区切られた右側のやつです。 100px とか 2em とか #ff0000 とかですね。
JavaScript だと CSSStyleDeclaration オブジェクトプロパティ値のこと

こんなところです

間違えないようにしましょう(自戒

CSS3 のカスケーディングについてのおさらい

いつも忘れて、毎回どうだったっけって確認するのがめんどいのでまとめておきます。

仕様

参照する仕様は CSS3 とします。(ワーキングドラフトなので、この内容は変わる可能性があります)
CSS3 の仕様は以下から探してください
site:http://www.w3.org/TR/css3 - Google Search

カスケーディングとは

カスケーディングに関する言葉の意味は以下に書いてあります。

CSS allows several style sheets to influence the rendering of a document, and the process of combining these style sheets is called “cascading”.

http://www.w3.org/TR/css3-cascade/#abstract

超意訳すると

CSS では一つの HTML *1に対して複数のスタイルシートを使えます。そして、複数のスタイルシートを結合するプロセスのことを「カスケーディング」と呼びます。

ということです。
もっと簡単に言うと

  • 要素の見た目って複数の link 要素とか style 要素とか style 属性でいっぱい宣言するよね!
    • するする!
  • 宣言が被ったときに、どの宣言を適用するかを決めないといけないよね!
    • うんうん!
  • その宣言選択方法のことを「カスケーディング」っていうよ!
    • ひゃー!

ということです。

実際のカスケーディングを解説

カスケーディングの手順は以下に書いてあります。

The purpose of cascading is to find one winning declaration among the set of declarations that apply for a given element/property combination.
User agents must sort declarations according to the following criteria, in order of importance:

  1. Weight. In CSS3, the weight of a declaration is based on the origin of the declaration and its marked level of importance. See a separate section below for how to compute the weight. The declaration with the highest weight wins.
  2. Specificity. The Selectors module [SELECT] describes how to compute the specificity. The declaration with the highest specificity wins.
  3. Order of appearance. The last declaration wins. Rules in imported style sheets are considered to be before any rules in the style sheet itself. Rules in two imported style sheets are considered to be in the same order as the @import rules themselves.

The sorting process continues until one winning declaration is found.

http://www.w3.org/TR/css3-cascade/#cascading

超意訳すると

カスケーディングは、与えられた宣言の中から「勝つ宣言」を見つけることです。
すべての宣言を以下の順で、勝敗を決めていきます。

  1. 「weight(優先度)」の高い宣言が勝ちです。
  2. 「weight(優先度)」が同じものは、「specificity(詳細度)」の高い宣言が勝ちです。
  3. 「weight(優先度)」と「specificity(詳細度)」が同じものは、最後に宣言された宣言が勝ちです。(@import の場合は @import された CSS は @import した CSS より先にあるということになります、そして、 @import された CSS が複数ある場合は、 @import された順に宣言されているという風になります。)

ということです。
もっと簡単にいうと

  • この要素には width に関する宣言がいっぱい定義されているなあ。
    • 困ったね!
  • じゃあ、一番 weight(優先度)が高い宣言を適用するよ
    • でも、weight(優先度)が同じ宣言が三つもあるよ
  • じゃあ、その中で一番 specificity(詳細度)が高い宣言を適用するよ
    • でも、weight(優先度)も specificity(詳細度)も同じ宣言が二つあるよ
  • じゃあ、後に出てきたほうの宣言を適用するよ
    • ひゃー!

ということです。
(ちなみに、ショートハンドな宣言がある場合は、複数の宣言がそのルールセットに宣言されていると思えばいいです。)

weight(優先度)とは何か

めんどくなってきたので、仕様へのリンクはやめます。
優先度とは、その宣言が含まれるスタイルシートが誰によって与えられたかという情報と !important の有無によってきまります。
誰によって与えられたかは以下の三つのパターンがあります。

  • ウェブサイトによって与えられた
  • ウェブブラウザによって与えられた
  • ウェブブラウザのユーザによって与えられた(ユーザがブラウザの設定をカスタマイズしてたりする場合)

で、優先度が高い順に並べると

  • !important が含まれるユーザによって与えられた宣言
  • !important が含まれるウェブサイトによって与えられた宣言
  • !important が含まれないウェブサイトによって与えられた宣言
  • !important が含まれないユーザによって与えられた宣言
  • ウェブブラウザによって与えられた宣言

となります。

speficity(詳細度)とは何か

詳細度の高低は以下のように決まります。*2

  1. まず、ID セレクタ(#hogehoge, #fugafuga)の数が多いルールセットに含まれる宣言のほうが詳細度が高い
  2. 次に、ID セレクタ(#hogehoge, #fugafuga)の数が同じなら、クラスセレクタ(.hogehoge, .fugafuga)、属性セレクタ([title="hoge"], [class$="fuga"])、疑似クラスセレクタの合計数が多いルールセットに含まれる宣言のほうが詳細度が高い。
  3. 次に、IDセレクタの数と、クラスセレクタと属性セレクタと疑似クラスセレクタの合計数が同じなら、要素名セレクタ(div, ul, li)の数が多いルールセットに含まれる宣言のほうが詳細度が高い。
  4. 最後にこれらのすべてのセレクタ数が同じのなら、詳細度は同じ
  5. ちなみに、全称セレクタ(*)や疑似要素セレクタ(::before)は詳細度に影響を与えない。

style 属性

style 属性のことは、 CSS2.1 ではちゃんと詳細度として定義されているのですが、 CSS3 では何故か消されています。何故?
style 属性に書かれた宣言は実際の挙動としては、以下のような挙動をします。

  • ウェブサイトで宣言された宣言と style 属性で宣言された宣言を比べると style 属性の宣言が勝ちます
  • ウェブサイトで宣言された !important 宣言と style 属性で宣言された !important 付きの宣言を比べると style 属性の宣言が勝ちます

まとめ

もう忘れません

*1:CSS は HTML だけの仕様ではないので、本来は「文章(document)」とするべきだが、ここでは無駄に混乱させないために HTML と言い切った。

*2:僕が分かりやすいと思う書き方で書いてます。仕様と書き方が違いますが、同じことを示しています。

IE8 で実装された Selectors API とは何か?

はじめに

IE8 には Selectors API という新しい仕様が実装されました。
ということで、今後 DOM 操作 API の主流になるであろう Selectors API についてまとめておきます。

Selectors API が使えるブラウザ

2008 年 3 月 6 日現在の一覧

Selectors API とは

Selectors API とは W3C で定義された仕様です。詳細に関してはこちらをどうぞ
簡単に説明すると
getElementsByTagName や getElementsByName や getElementById などの DOMオブジェクトを取得するメソッドありますよね。
あれの「すごい版」の querySelector や querySelectorAll というメソッドのことを指します。
たとえば、既存の getElementsByTagName などのメソッドの代わりに Selectors API を使うと以下のようになります。

// 以下の二つは同じ要素を取得する
var nl00 = document.getElementsByTagName('div');
var nl01 = document.querySelectorAll('div');

alert(nl00[0] == nl01[0]); // true
alert(nl00[1] == nl01[1]); // true
  :
  :

// 以下の二つは同じ要素を取得する
var elm00 = document.getElementById('target');
var elm01 = document.querySelector('#target');

alert(elm00 == elm01); // true

// 以下の二つは同じ要素を取得する
var nl02 = document.getElementsByName('hoge');
var nl03 = document.querySelectorAll('*[name="hoge"]');

alert(nl02[0] == nl03[0]); // true
alert(nl02[1] == nl03[1]); // true
  :
  :

どうですか?簡単ですね?

querySelector や querySelectorAll には、 CSS セレクタを渡す。

前の例を見て気が付く方も多いと思いますが、 querySelector や querySelectorAll には CSS セレクタを渡します。
CSS セレクタとは、 CSS の中カッコの前に書かれる要素を特定するための文字列のことですね。
たとえば、以下の例で言うと

ul.navigation li {
  display: inline;
}

「ul.navigation li」が CSS セレクタにあたります。

(追記) 疑似クラスの扱い

id:vantguarde さんから以下のようなはてブコメントを頂きましたので、疑似クラスについて追記します。id:vantguarde さんありがとうございます!

擬似クラスなノードも取得できるってのを書いてほしいかも。

はてなブックマーク - vantguardeのブックマーク / 2008年3月6日

おっしゃるとおり Selectors API では疑似クラスにも対応しています。
たとえば、

// 現在選択されている要素を取得
var elm00 = document.querySelector(':focus');

// 現在マウスが乗っかってる要素を取得
var elm01 = document.querySelector(':hover'); // elementFromPoint と等価

このような感じになります。
ただ、プライバシーの配慮か単なるバグなのかは分かりませんが IE8 では :visited 疑似クラスは取得できませんでした。

alert(document.querySelectorAll('a:visited').length); // 常に 0
(追記) カンマで複数の CSS セレクタを指定できます。

カンマ自体が CSS セレクタの仕様の一部なので、当たり前と言えば当たり前ですが、これも書いておきます。

// 以下のように、カンマを使って複数の CSS セレクタを指定することもできます。
document.querySelectorAll('span, strong'); // span と strong 要素をすべて取得

querySelector と querySelectorAll の違い

querySelector と querySelectorAll の違いは、これも、最初の例で分かると思いますが、取得する要素が一個か複数かの違いです。
querySelector の場合は、そのセレクタが選択する要素の一番最初の要素だけを取得し、 querySelectorAll の場合はすべての要素を取得します。
例えば、以下のような HTML があったとします。

<html>
 ..(略)..
 <body>
  <ul><li>aaa</li><li>bbb</li><li>ccc</li></ul>
 </body>
</html>

そうすると、 querySelector は以下のようになります。

var elm = document.querySelector('body > ul > li');
alert(elm.innerHTML); // aaa

このように、 querySelector は HTML を上から順*1に見て一番最初に出て来た要素を選択します。
また、 querySelectorAll は以下のようになります。

var nl = document.querySelectorAll('body > ul > li');
alert(nl.length); // 3
alert(nl[0].innerHTML); // aaa
alert(nl[1].innerHTML); // bbb
alert(nl[2].innerHTML); // bbb

このように、 querySelectorAll も HTML を上から順*2に格納しています。
つまり、

document.querySelector(selector) // は
document.querySelectorAll(selector)[0] // と等価

ということです*3

querySelectorAll で取得される値

次は、 querySelectorAll で取得される値についてです。
この値は、配列のように扱えますが、配列ではありません。

// すべての要素を取得
var nl = document.querySelectorAll('*');

alert(nl instanceof Array); // false

// push などの配列のメソッドは使えない
try { nl.push('hoge') } catch (e) { alert('error') } // error

// 使えるのは length だけ
alert(nl.length);

ここまでは、従来の getElementsByTagName などで取得される値と同じです。
しかし、 querySelectorAll で取得される値の性質は getElementsByTagName などで取得される値とも少し違っています。

// すべての要素を取得
var nl00 = document.getElementsByTagName('*');
var nl01 = document.querySelector('*');

// ここで、 HTML の内容を書き換える(要素を一つ削除する)
document.body.removeChild(document.body.getElementsByTagName('*')[0]);

alert(nl00.length == nl01.length); // false

alert(nl00.length); // こちらは、変更が反映されて一つ少ない値になる
alert(nl01.length); // こちらは、変更が反映されない(取得時の状態)

このように、取得後に HTML の構造が変化した場合その変化を反映させるかさせないかの違いがあります。
また、それぞれのオブジェクトの名前を NodeList 、 StaticNodeList といいます。

NodeList getElementsByTagName などで取得される値 値取得後の HTML の構造の変化をその場で反映する
StaticNodeList querySelector で取得される値 取得時の状態のままで、変化が反映されない

一見、 getElementsByTagName の仕様のほうが便利に思えますが、この動的な性質があったせいで処理が重くなっていたんですね。
だから、 querySelectorAll は取得した値の操作が軽いかもしれませんね。

document だけじゃなく、要素からも使えます。

今までは、 document.querySelectorAll とやってきましたが、普通に要素でも使えます。
要素で、実行した場合は子孫要素から要素が選択されるようになります。

var elm = document.querySelector('#target');

// elm の子孫要素で hoge というクラスを持つ要素
elm.querySelectorAll('.hoge');  // つまり、 #target .hoge と同じ

まとめ

こんな感じです!超便利ですね!
DOM 3 XPath 派な僕ですが。やっぱり IE8 が Selectors API を実装したので、これから Selectors API の時代が来ると思います。
他のブラウザも早く実装して欲しいですね!
この便利なメソッドが使えるようになるのは非常に楽しみです><
ではでは!
(Namespace については省略しました。)

*1:このような順番のことをドキュメント順と言う

*2:このような順番のことをドキュメント順と言う

*3:もちろん、結果が等価ということであって、実行効率とかは違うかもしれません。

display: table-cell を使ったマルチカラムレイアウト

Acid2 Test で

IE8 が display: table-cell をきちんとレンダリングするということが分かったので。

IE8 が普及する(笑)のを 5 年くらい先取りして

display: table-cell によるマルチカラムレイアウトを書いてみました。
リンクは以下です。
http://amachang.art-code.org/tablecell/
今のところ Opera 9+、Safari 2+、 Firefox 2+ で正常にレンダリングされることを確認しました。

ポイント

ソースを見て分かるように HTML も CSS もめちゃめちゃシンプルです。
ポイントは display: table-cell の要素が兄弟要素の高さに併せて間延びするところでしょう。
これで、何カラムだろうとお手のものです。

IE8 はある意味 Acid2 Test をクリアしていない

HTML5 の ML でこんな記述がありました。

Acid2 Test にこの meta タグないから IE8 合格してなくね?(意訳)

<meta http-equiv="X-UA-Compatible" content="IE=8" />

うまいこというなあと思った