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 の「値」とは何か
この前
以下のようなエントリを書きました
CSS の名前の整理 - IT戦記
6 つの値
CSS において「値」は曖昧な言葉です。
なので、「値」という言葉を使って CSS の説明をするのは非常に効率が悪かったりします。
たとえば、「ここの値って、 hogehoge の意味の値?」「いやいや、ここの fugafuga の意味の値が piyopiyo なんだよ」というように、まったく無駄な会話が繰り広げられるわけです。
ですので、今日からはちゃんと CSS の「値」をちゃんと説明できるように、「値」に以下の 5 つの名前を付けましょう。
- Declared values
- Cascaded values
- Specified values
- Computed values
- Used values
- 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 が与えられれば暗算することも可能なはずです。
ここら辺もいろいろあるので、そのうちまとめたいと思います。
また
JavaScript で CSSStyle* 系のオブジェクト をいじる場合は
- どの 要素 or ルールセット の
- どの プロパティ を
- どの 単位 で
書き換えれば、無理のないレンダリングが出来るかを考えるのが非常に重要です。
たとえば、ここをいじったせいでこっちもいじらければならない><
というようなことを無くすことができ、パフォーマンスを何倍にも向上させることができます(マジで
この知識はそういう場面でも威力を発揮します。
まとめ
もう、 CSS の値については完璧ですね!
この説明、自分で書いててめっちゃ難しかったです><
CSS の名前の整理
CSS の話をする時に
名前をはっきりさせてないと混乱するので、整理しておこう。自分用です。けっこう適当です。
仕様
ここの仕様を見て書いています。
CSS Syntax Module Level 3
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:
- 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.
- Specificity. The Selectors module [SELECT] describes how to compute the specificity. The declaration with the highest specificity wins.
- 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
超意訳すると
カスケーディングは、与えられた宣言の中から「勝つ宣言」を見つけることです。
すべての宣言を以下の順で、勝敗を決めていきます。
ということです。
もっと簡単にいうと
- この要素には width に関する宣言がいっぱい定義されているなあ。
- 困ったね!
- じゃあ、一番 weight(優先度)が高い宣言を適用するよ
- でも、weight(優先度)が同じ宣言が三つもあるよ
- じゃあ、その中で一番 specificity(詳細度)が高い宣言を適用するよ
- でも、weight(優先度)も specificity(詳細度)も同じ宣言が二つあるよ
- じゃあ、後に出てきたほうの宣言を適用するよ
- ひゃー!
ということです。
(ちなみに、ショートハンドな宣言がある場合は、複数の宣言がそのルールセットに宣言されていると思えばいいです。)
weight(優先度)とは何か
めんどくなってきたので、仕様へのリンクはやめます。
優先度とは、その宣言が含まれるスタイルシートが誰によって与えられたかという情報と !important の有無によってきまります。
誰によって与えられたかは以下の三つのパターンがあります。
- ウェブサイトによって与えられた
- ウェブブラウザによって与えられた
- ウェブブラウザのユーザによって与えられた(ユーザがブラウザの設定をカスタマイズしてたりする場合)
で、優先度が高い順に並べると
- !important が含まれるユーザによって与えられた宣言
- !important が含まれるウェブサイトによって与えられた宣言
- !important が含まれないウェブサイトによって与えられた宣言
- !important が含まれないユーザによって与えられた宣言
- ウェブブラウザによって与えられた宣言
となります。
speficity(詳細度)とは何か
詳細度の高低は以下のように決まります。*2
- まず、ID セレクタ(#hogehoge, #fugafuga)の数が多いルールセットに含まれる宣言のほうが詳細度が高い
- 次に、ID セレクタ(#hogehoge, #fugafuga)の数が同じなら、クラスセレクタ(.hogehoge, .fugafuga)、属性セレクタ([title="hoge"], [class$="fuga"])、疑似クラスセレクタの合計数が多いルールセットに含まれる宣言のほうが詳細度が高い。
- 次に、IDセレクタの数と、クラスセレクタと属性セレクタと疑似クラスセレクタの合計数が同じなら、要素名セレクタ(div, ul, li)の数が多いルールセットに含まれる宣言のほうが詳細度が高い。
- 最後にこれらのすべてのセレクタ数が同じのなら、詳細度は同じ
- ちなみに、全称セレクタ(*)や疑似要素セレクタ(::before)は詳細度に影響を与えない。
style 属性
style 属性のことは、 CSS2.1 ではちゃんと詳細度として定義されているのですが、 CSS3 では何故か消されています。何故?
style 属性に書かれた宣言は実際の挙動としては、以下のような挙動をします。
まとめ
もう忘れません
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
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 と同じ