2008年 9月 21日(日曜日) 09:42

【CSS Tips】ネストされたボックス要素のmargin-topのブラウザ毎の解釈の違い-Margin Collapsing

評価:
(0 票)

親要素のコンテンツ領域を基準にして子要素の上と左のマージンを確保しようとしたとき、ブラウザの違いでなかなか思うように配置できませんでした。

ネストされたDIV要素にmargin-topを指定すると、IE7とFireFox等の標準的なブラウザとでは解釈が異なるようです。結局のところ、これはCSS2の仕様の"8.3.1. Collapsing margins"を正しく実装しているかどうかに拠っていたようです。ここでは実例として、下のようにネストされたDIV要素にいろいろなスタイルを適用した例を掲載します。

目標は、どのブラウザでも次のような配置になるようにすることです。

1. マージンなどを指定しないネスト

#outer {
    width: 100px;
    height: 180px;
    background-color: #fcc; // ピンク
}
#inner1 {
    width: 60px;
    height: 60px;
    background-color: #cfc;  // 黄緑
}
#inner2 {
    width: 60px;
    height: 60px;
    background-color: #ccf; // 水色
}

この段階ではどのブラウザでも同じように表示されます。

2. innerにマージンを指定すると・・・

#outer {
    width: 100px;
    height: 180px;
    background-color: #fcc;
}
#inner1 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}
#inner2 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}

IEではinner1の上端とouterの上端の間にマージンが空くのに対し、FireFoxではouterの上にマージンが空いてしまい、inner1とouterの上端が揃う形になってしまいます。inner1とinner2の間はどちらも20pxのマージンが空きます。

なぜこのようなことが起こるのかは、冒頭で述べたCSS2の仕様"8.3.1 Collapsing margins"に拠るようです。これによると、"Collapsing margins"とは、「空でないコンテンツ、ボーダー、パディングまたはclearで分けられていない、並列またはネストによって隣接した二つ以上のボックスのマージンは結合されて一つになる。」という仕様らしいです。

In this specification, the expression collapsing margins means that adjoining margins (no non-empty content, padding or border areas or clearance separate them) of two or more boxes (which may be next to one another or nested) combine to form a single margin.

In CSS 2.1, horizontal margins never collapse.

Vertical margins may collapse between certain boxes:

  • Two or more adjoining vertical margins of block boxes in the normal flow collapse. The resulting margin width is the maximum of the adjoining margin widths.

...以下略

水平方向にはCollapseせず、垂直方向のみCollapseして、結合されたマージンの大きさは、元のボックスのマージンのうち最大の値になるようです。

今回の例の場合、この仕様に準拠しているブラウザではouterとinnerのマージンが結合して一つとなり、結合されたマージンはouterの外側に表示されるため、結果としてコンテンツ領域の上端が揃って表示されます。IEではこれが正しく実装されていないために、marginがcollapseしない状態で誤って表示されてしまっているのでした。

3. outerにボーダー、innerにマージンを指定すると・・・

#outer {
    border: 1px solid #000;
    width: 100px;
    height: 180px;
    background-color: #fcc;
}
#inner1 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}
#inner2 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}

今度はouterのmarginがinnerのmarginとはborderで分断されてマージンが結合されないため、IE、FireFox共に、outerとinner1の間にマージンが空きます。FireFoxでも親要素にborderを指定した場合はouterの上にはマージンは空かなくなりますが、親要素にボーダーを指定したくない場合にはこの方法は使えません。

4. outerにパディング、innerにマージンを指定すると・・・

#outer {
    padding: 1px;
    width: 100px;
    height: 180px;
    background-color: #fcc;
}
#inner1 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}
#inner2 {
    margin: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}

今度もborderを指定した場合と同様に、outerのmarginがinnerのmarginとはpaddingで分断されてマージンが結合されないため、IE、FireFox共にouterとinner1の間にマージンが空きます。しかし、paddingを使いたくないときはこの方法は使えません。(私的には古いIEのボックスモデルの解釈の違いのため、パディングは極力使わないようにしています。)

5. innerのポジションをrelativeにして、topとleftを指定すると・・・

#outer {
    width: 100px;
    height: 100px;
    background-color: #fcc;
}
#inner1 {
    position:relative;
    top: 20px;
    left: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}
#inner2 {
    position:relative;
    top: 20px;
    left: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}

IE、FireFox共に、outerとinner1の間にスペースが空きます。しかし、inner2の位置はinner1のポジションがstaticである場合の位置が基準となるため、このような設定ではinner1とinner2の間にはスペースは空きません。inner1の高さがわかる場合はinner2のtopの値を調節すればよいのですが、inner1の高さがコンテンツの量で変わる場合はこの方法は使えません。

6. inner1のポジションをrelativeにして、topとleftを指定、inner2にmarginを指定すると・・・

#outer {
    width: 100px;
    height: 100px;
    background-color: #fcc;
}
#inner1 {
    position:relative;
    top: 20px;
    left: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}
#inner2 {
    margin-top: 40px;
    margin-left: 20px;
    width: 60px;
    height: 60px;
    background-color: #cfc;
}

これで目標のレイアウトができました。ポイントはinner2の上のマージンはinner1のポジションがstaticであった場合を基準に配置されることです。実際にはinner1は20px下の位置に表示されるので、inner2のマージンは20px増やした40pxを指定することです。

私的にはIEをサポートするために今後はボーダーのないネストされたボックス要素の子要素には上下のmarginを使わないことにします。

7. 応用編: ボックスの高さがコンテンツの量で変化する場合

ボックスの高さがコンテンツの量によって高さが変わる場合はどのようにしていすればよいでしょうか?上下のマージンは使わないことにすると宣言したばかりですが、試しに下のようにinner2のmargin-bottomを指定してみます。

#outer {
    width: 100px;
    background-color: #fcc;
}
#inner1 {
    position:relative;
    top: 20px;
    left: 20px;
    width: 60px;
    background-color: #cfc;
}
#inner2 {
    margin-top: 40px;
    margin-left: 20px;
    margin-bottom: 20px;
    width: 60px;
    background-color: #cfc;
}
朝焼けが鮮やかな明け方の空は、赤から青へ明るさを増す。
胃が痛くていらいらしている犬が、意味もなく威嚇している。

IEとFireFoxでこの表示を比べると、やはりmargin-bottomの場合も、margin-topと同じように表示が異なってしまいます。FireFoxではマージンの結合によりinner2とouterの下端が揃ってouterの下にマージンが空くのに対し、IEではinner2とouterの間にスペースが空きます。

ただ、実際にはこの例のように高さが変化してもよい場合は上下のパディングを使っても問題ないので、outerとのスペースを空けるためには普通にパディングを使うことにします。左右はouterの幅を固定したいので、古いIEを考慮してouterのpaddingではなくinnerのmarginで指定します。

#outer {
    padding: 20px 0;
    width: 100px;
    background-color: #fcc;
}
#inner1 {
    margin-top: 0;
    margin-right: 20px;
    margin-bottom: 20px;
    margin-left: 20px;
    width: 60px;
    background-color: #cfc;
}
#inner2 {
    margin-top: 0;
    margin-right: 20px;
    margin-bottom: 0;
    margin-left: 20px;
    width: 60px;
    background-color: #cfc;
}
朝焼けが鮮やかな明け方の空は、赤から青へ明るさを増す。
胃が痛くていらいらしている犬が、意味もなく威嚇している。

innerの数が3つ以上の場合は下のようになります。

#outer {
    padding-top: 20px;
    padding-bottom: 20px;
    width: 100px;
    background-color: #fcc;
}
#inner1 {
    margin-top: 0;
    margin-right: 20px;
    margin-bottom: 20px;
    margin-left: 20px;
    width: 60px;
    background-color: #cfc;
}
#inner2 {
    margin-top: 0;
    margin-right: 20px;
    margin-bottom: 20px;
    margin-left: 20px;
    width: 60px;
    background-color: #cfc;
}
#inner3 {
    margin-top: 0;
    margin-right: 20px;
    margin-bottom: 0;
    margin-left: 20px;
    width: 60px;
    background-color: #cfc;
}
朝焼けが鮮やかな明け方の空は、赤から青へ明るさを増す。
胃が痛くていらいらしている犬が、意味もなく威嚇している。
ウイリアムはウイークエンドにうきうきしてウインタースポーツをしに行く。

結局、ネストされたボックス要素の上下のスペースを調節する際は、クロスブラウザを考慮して次のような方針に従うことにします。

  1. 高さを指定しない場合は親要素のpaddingを使う。
  2. 高さを指定する必要がある場合は、子要素のポジションをrelativeにして、topを使う。

スタイルシートによるレイアウトは本当にややこしいですね。

最終更新日: 2011年 6月 27日(月曜日) 01:38
くらち たかよし

くらち たかよし

モバイル・Webアプリ作家。最近は主にiPhoneアプリ制作を手がける。企画から、UIデザイン、設計、実装、テスト、多言語対応、ユーザーサポートまでを1人〜数人の個人で行う全人的開発手法の確立を目指している。

使う言語はObjective-C, C++, C#, Java, PHPなど。Web関連で使うものはCakePHP, MySQL, Joomla! CMSなど。デザインはシロウトながらPhotoshopとIllustratorをなんとかがんばって使う。

場所や時間に縛られない、インターネット時代の新しい働き方、自由な生き方を模索中。海外移住、低予算&低リスク起業、キャリアデザイン、心理学などにも興味あり。

Web: awaresoft.jp/