凄く今更な話だけれども、数年くらいWEB制作から離れていると見落としがちな常識の転換。
旧世代CSS:要素の幅・高さはあらかじめ余白と境界を引いた値にしよう
CSSで要素に対して幅や高さを指定する際に、要素の周囲を囲むborderの太さならびに境界内部の余白paddingの値は、あらかじめ目星をつけておかないといけない。というのも、widthやheightといったプロパティで指定された値に、borderとpaddingそれぞれの幅を加えたものが要素が親要素内で占有する大きさとなるからである。
旧padding,border計算方法
上図の例で言うと、要素に対してwidthとheightで指定された幅200px高さ150pxという値は、余白paddingや境界線borderの幅がいくら変わっても保証される。その代わり要素自体の大きさは指定幅に加算されていくので、たとえば親要素の幅が400pxのところにこの.classが指定されたボックスをfloatを使って2つ並べたい場合、paddingとborderの幅を取ってしまうと2つ目のボックスが下に落ちてしまう。
そこで、ボックス落ちを防ぐためにはwidthとheightの値を、あらかじめpaddingとborderにあてがう幅分引いて指定しないといけない。上の例で言うならば、widthを150(200-20*2-5*2)px、heightを100(150-20*2-5*2)pxで指定しないと、ボックス落ちである。そして後々ページの見映えを変えたくて、paddingやborderの幅を変更したくなったらもう大変。変更の都度widthとheightの値を動かさないといけない。うわー面倒臭い。
かつてこんな感じに解決してました
この面倒臭さを解決する方法。一つは、クライアントにレビューの際にIE6を使わせる。IE6の場合paddingとborderはwidth,heightから引くという超モダンな解釈をしてくれる。そこで上の例で言えばwidthを200px、heightを150pxと指定しておいて、後からpaddingとborderを変更してもボックス落ちしない。クライアントには、IE6こそが先進的ブラウザで、その他99.9%のブラウザはゴミですと言い聞かせよう。
と、まあ冗談はさておき。問題を分割して、padding部分だけでも解決する場合。要素内にもう一つの要素を放り込んで、余白部分は内側の要素のmarginで指定するようにする。inner_**とかそういった命名の要素があれば、この問題を解決しようと苦心している可能性が高い。
そしてもう一つスマートな解決法として、CSSの変数を使用するというものがあった。CSS標準のものであれば、CSS Variables。
:root{
--tekitou-padding-value: 20; /* 変数名は--から始まる適当な名前 */
--tekitou-border-value: 5;
}
.class{
width: calc(200 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
height: calc(150 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
padding: var(--tekitou-padding-value);
border: var(--tekitou-border-value);
/* var(変数)で変数を定義された値に展開 */
/* calc(数式)で数式を展開 */
} |
:root{
--tekitou-padding-value: 20; /* 変数名は--から始まる適当な名前 */
--tekitou-border-value: 5;
}
.class{
width: calc(200 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
height: calc(150 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
padding: var(--tekitou-padding-value);
border: var(--tekitou-border-value);
/* var(変数)で変数を定義された値に展開 */
/* calc(数式)で数式を展開 */
}
このようにすれば、後でpaddingとborderの値を変更しても、自動的にwidthとheightの値も連動する。
ただし、このCSS Variablesやcalc関数については対応ブラウザの問題がある。ベンダープレフィックスをつけないと動かない可能性は勿論のこと、CSS Variablesの方はEdgeの登場によりお役御免となったIEでサポートされてないのが痛い。ということで、変数を使う場合は主にSassやLESSのようなCSSプリプロセッサに頼ることになるだろう(コード例は省略)。CSSプリプロセッサを使う理由の全てがこの変数と数式計算という人も、きっと多いはず。
新世代CSS:box-sizing : border-boxで余白と境界をサイズに含めない
この余白と境界の値をあらかじめ考慮しないとボックス落ちしてしまうという仕様は、レスポンシブデザインにおいてはたいへん都合が悪かった。というのも、画面幅に応じて要素の幅も伸縮するレスポンシブデザインでは、幅の指定にパーセントを使用するからである。ボックス落ちしない余白と境界の値をあらかじめ知るのは不可能なので、デザインに遊びを持たせるか、フラットデザインのようにして影響を最小限にするなどしなければならなかった。勿論、CSSプリプロセッサで多少解決出来る部分もあるけれど、それはそれ。
かつてIE6的な余白と境界の解釈を標準化という錦の御旗のもと根絶してしまってから、実はそっちの方が都合が良かったと気付いたのだ。そこでCSS3ではbox-sizingというプロパティが出来て、余白と境界をサイズに含めない計算方法も選べるようになった。
box-sizing : border-boxを指定した場合の計算方法
指定方法は、box-sizingプロパティの値をborder-boxと指定するだけ(無指定の値はcontent-boxで、これは今までの計算方法だ)。これにより要素のwidthとheightで指定されたサイズが保証されて、paddingとborderはその値から減算することになる。後付けで値の変更を行っても、影響は最小限だ。
box-sizing : border-boxをどこで宣言するか
ちなみにこのbox-sizingプロパティにはinheritという値もあり、これが指定されていると上位要素で宣言された値を継承する。ページ全体にわたってbox-sizingを有効にする場合、一番簡単なのは、全称セレクタを使って全要素に明示的に指定することだけれども、
/* 全称セレクタだけでは疑似要素に適用されないので、beforeとafterもついでに指定 */
/* 疑似要素使わないなら指定の必要なし */
*, *:before, *:after{
box-sizing: border-box;
} |
/* 全称セレクタだけでは疑似要素に適用されないので、beforeとafterもついでに指定 */
/* 疑似要素使わないなら指定の必要なし */
*, *:before, *:after{
box-sizing: border-box;
}
ただこれだとcontent-box指定を前提として書かれたCSSを拝借してくる場合などに、対象となる全要素をいちいちcontent-box指定に直すのが面倒臭い。そこで、inherit指定を上手く活用した方がbetter。
/* 最上位要素のhtmlに対してborder-boxを指定 */
html{
box-sizing: border-box;
}
/* 全ての要素のbox-sizingがinheritとなるように指定 */
*, *:before, *:after{
box-sizing: inherit;
} |
/* 最上位要素のhtmlに対してborder-boxを指定 */
html{
box-sizing: border-box;
}
/* 全ての要素のbox-sizingがinheritとなるように指定 */
*, *:before, *:after{
box-sizing: inherit;
}
このようにすれば、拝借してきたCSSがcontent-box指定の場合でも、その上位要素に対してcontent-boxを指定し直すだけでよい。
box-sizing : border-box入りのリセット/ノーマライズCSS
全称セレクタで全要素に対して指定をするならば、どうせならリセット/ノーマライズCSSで同時にこれをやってくれるものの使用も選択肢として考えたい。確認した範囲で以下のリセット/ノーマライズCSSが最新版で対応している(リンク先はGitHub)。
- sanitize.css(全要素にinherit&html要素に対してborder-box指定)
- minireset.css(全要素にinherit&html要素に対してborder-box指定)
- ress(全要素にinherit&html要素に対してborder-box指定)
- Marx(全要素にinherit&:root疑似要素に対してborder-box指定)
で、上の画像にも付記してあることだけれども、みんな大好きなTwitter Bootstrapではv2.3.2までにはborder-box指定がなく、2013年8月にリリースされたv3.0.0以降からborder-box指定が入る(全要素にinherit&html要素に対してborder-box指定)。
世代間の壁の存在に留意が必要
そんなわけで、昨今WEB制作の道に入門してくる人達は完全にborder-boxの常識でコーディングを行うだろうし、それ以前の常識で書かれたコードやWEB上のサンプルコードなどはcontent-boxの場合が多い。ここで混乱が起こる可能性がありそう(特にWordPressテーマとプラグインの関係で顕著)。
一応頭でinherit指定を行っておけば世代の異なるCSSコードの付け足しの際、直上の親要素に明示的指定を打って流用することが出来るけれども、付け足しが多数になってくるとあとあと訳が分からなくなってしまう。
そこで、出来ることならば新世代の常識の方に寄せる方向で旧世代コードも手直ししておいた方が良いのではないかと思う。Bootstrap等が軒並み新世代であることや、ブラウザのベンダープリフィックスなしbox-sizing対応がほぼ完了したことも鑑みて(box-sizingの各ブラウザ実装状況)。あと、購入する参考書の発行年などにも今後注意が必要だね。