CSS: marginの正しい理解
toggle()や変数、calc、:matchなど、今までにないCSSプロパティ、セレクタが提案・実装されて、CSS3, CSS4も楽しくなってきています。
border-radiusや、box-shadowなども、古いAndroidブラウザ以外なら、prefixなしで使える状況も増えてきました。
最新技術は、これから必要になってくるかもしれませんが、基本も大切です。
float や position など、CSSコーディングを悩ませるタネはいくつもありますが、今回はその中でも私がCSSで一番難しいと思う margin について書きます。
「marginはバグが多い」という声をたまに聞きます。
しかし話を聞いてみると、正常な動作をバグと間違って認識しているケースもあります。
marginを正しく理解することによって、効率的なレイアウトを構築できますので、基本的な内容ですが、読んで頂ければ幸いです。
ボックスモデル
marginを説明する上で、まずはボックスモデルを知る必要があります。
CSSを適用する場合、以下の図を理解する必要があります。
CSSでmarginを指定すると、上記のボックスモデルのmarginと書かれた部分に、数値が適用されます。
marginの種類
marginにはプロパティが用意されており、省略も可能です。
個別指定
- margin-top
- margin-bottom
- margin-left
- margin-right
marginの省略
- margin:0; (上下左右)
- margin: 0 0; (上下) (左右)
- margin:0 0 0; (上) (左右) (下)
- margin:0 0 0 0; (上) (右) (下) (左)
論理プロパティ
- margin-start
- margin-end
- margin-before
- margin-after
margin-topなどは見慣れてるけど、margin-startは初めて見るという方もいると思います。
margin-(start|end|before|after) は回転方式のブロックに対して、提案されたものです。
margin-topは、画面から見た物理的な方向に対し、
margin-startは、論理的な方向になります。
横書きにmargin-startが指定されている場合は、margin-leftが適用されます。
縦書きになった場合は、margin-startは、margin-topが適用されることになります。
このプロパティを使用することにより、同一のCSSで縦書き、横書きの切り替えがスムーズになる可能性を持っています。(ただしIE8などは使用不可。)
margin と auto と width の関係
marginに対して、autoを設定した場合は数値は0になります。
しかし、横幅(width)を指定した状態で、marginの左右のどちらかにautoを指定すると、指定した方に数値を算出します。
ブラウザの幅が600pxの場合
div {
with:100px;
margin-left:auto;
}
上記の例なら、ブラウザの幅が600pxなので、margin-leftは自動で500pxになります。
marginの中央配置
横幅を指定した状態で、margin-left, margin-right にautoを指定した場合。
左右等分のmarginが算出され、ボックスは中央に配置されます。
Negative Margin(ネガティブ・マージン)
marginの数値をマイナス指定することによって、要素を移動や、要素を配置します。
パターンから外れたレイアウトを組む際に、お世話になることが多いです。
例えば、以下の図のようなレイアウトにしたい場合
<style>
/* css reset */
ul {
margin:0;
padding:0;
list-style:none;
}
/* demo */
#list {
margin:0 auto;
border:1px solid #000;
padding:8px 8px 0;
width:488px;
}
ul {
margin-right:-8px; /* ここでネガティブマージンを指定 */
/* width:496px; 追記:IE6,7を無視するならいらない */
overflow:hidden;
}
li {
float:left;
margin:0 8px 8px 0;
width:116px;
height:116px;
background-color:#bbb;
}
</style>
<div id="list">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
ネガティブマージンを使用すると、レイアウトの幅が広がります。
ただし上記ソースの1つ1つの意味が、理解できていないうちは、多用すべきではありません。
margin と padding
marginでも、paddingでも、空間をあけることが可能です。
<style type="text/css">
.margin {
margin-bottom:10px;
}
.padding {
padding-bottom:10px;
}
</style>
<div class="margin">aa</div>
<div class="margin">bb</div>
<div class="padding">aa</div>
<div class="padding">bb</div>
margin, padding共に、空間をあけることができます。
見た目は同じに見えますが、挙動は全く違います。
paddingは、背景色や、枠線にも影響して余白をあけることができますが、
marginは要素ごとで間隔をあけることができます。
marginの相殺
paddingにはない機能として、marginの相殺があります。
相殺はmargin同士が重なった場合に起こり、「marginが使いづらい」という印象を与える最大の理由かもしれません。
特定のケースだと、相殺が起きないなどトリッキーな動きをします。
トリッキーすぎて、初学者に「バグじゃないの?」と思われることも、しばしばです。
しかしmarginの相殺を理解することで、marginの単一方向の指定や、paddingを使用するよりも、HTML, CSSをクリーンに書ける場合も多くあります。
<style type="text/css">
.margin {
margin:10px 0; /* 上下に1emを指定 */
}
.padding {
padding:10px 0; /* 上下に1emを指定 */
}
</style>
<div class="margin">aa</div>
<div class="margin">bb</div>
<div class="padding">aa</div>
<div class="padding">bb</div>
paddingであれば、divの間は、10px+10px=20px となります。
marginは相殺によってdivの間が、10pxしかあきません。
相殺を理解するには、要素を人に例えるとわかりやすいかもしれません。
- widthは、骨格
- heightは、身長
- borderは、皮膚
- paddingは、脂肪
- marginは、心の距離
脂肪(padding)だと、物理的な距離となります。
これが心の距離(margin)の場合は、Aさんの心の距離が1。
ただしBさんのAさんのことを、それほど想っておらず、心の距離は3だとします。
Aさんは心が縮まっていると思っていても、AさんとBさんの心の距離を客観的に見ると、実際の心の距離は3となります。
marginの相殺の数値
相殺は、数値の大きい方が適用されます。
<style type="text/css">
.a {
margin:20px 0;
}
.b {
margin:30px 0;
}
</style>
<div class="a">a</div>
<div class="b">b</div>
上記の例では、div同士の間隔は、20pxと30pxで50pxになるはずですが、相殺によって、少ない方の数値が打ち消されます。
入れ子での相殺
相殺は入れ子の状態でも、marginが重なれば発生します。
両方ともにmarginが設定された場合、相殺が発生します。
<style type="text/css">
.parent{
margin:10px 0;
}
.child {
margin:30px 0;
}
</style>
<div class="parent">
<div class="child">child</div>
</div>
入れ子の別の例
<style type="text/css">
.parent {
margin:30px 0;
}
.child {
margin:10px 0 40px;
}
</style>
<div class="parent">
<div class="child">demo</div>
</div>
上記の例では、margin-topは、.parentの方が大きいので30pxが適用されます。
しかしmargin-bottomは、.childの方が値が大きいので40pxが適用されることになります。
相殺が起こった場合は、大きい方の値が優先されることを覚えておいて下さい。
相殺が起こらないケース
親の要素に特定のCSSを適用することで、子の相殺が発生しません。
- border (marginが指定されている方向)
- padding (marginが指定されている方向)
- overflow:hidden, scroll (auto, visibleは適用されない)
- position:absolute, fixed (relative, staticは適用されない)
さらに上記以外にも、floatによって相殺が発生しない場合があります。
margin と float の関係
marginとfloatは、密接な関係にあります。
floatを使いこなすためにも、marginの理解は必須です。
marginのかかっている要素に、floatを指定した場合
親、子に関わらずmarginを指定している要素に、floatを適用すると相殺をしなくなります。
また width と margin:auto; の関係で記載したように、widthとmarginを指定した場合のautoは、数値を自動算出します。
ただしfloatが指定されると、marginのautoは、必ず数値が0になります。
margin: auto;とfloatを指定した例
<style>
div {
margin:10px auto;
width:300px;
float:left;
}
.child {
margin:10px;
width:100px;
height:100px;
float:left;
}
</style>
<div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
- 親か子のどちらかでもfloatを指定すると、親子での相殺が起こらない。
- floatが指定されると、margin:auto; は絶対的に0になる。
- 兄弟同士にfloatが指定されていると、相殺が起こらない。
floatを使用すると、その要素のmarginはpaddingのような挙動を行います。
marginのかかっている要素に、floatが隣接している場合
floatが親、子の要素にかかると相殺が発生しません。
ただfloatが隣接しているだけなら、marginとfloatで相殺が発生します。
以下はその基本的な条件です。
- floatに隣接
- 隣接している要素に、marginが指定されている
- 隣接している要素に、floatが指定されていない
上の条件が全てではありません。
様々な影響により、相殺が発生する場合としない場合があるので割愛します。
以下、marginとfloatの相殺の例です。
<style>
img {
float:left;
margin:0 10px;
}
.test {
margin-top:96px;
}
</style>
<img src="dummy.jpg" alt="" width="80" height="80" />
<img src="dummy.jpg" alt="" width="80" height="80" />
<img src="dummy.jpg" alt="" width="80" height="80" />
<div class="test">floatとmarginの相殺</div>
1つの要素にmarginとfloatが指定されていると、marginの相殺が起きません。
しかし上の例のdiv.testは、marginとfloatが相殺する条件を満たしているので、図のような動きをします。
そしてこの相殺は、floatを解除するclearプロパティにも関係してきます。
clearは、floatを解除するものではない
「floatを解除する」とよく説明されるclearプロパティですが、floatに隣接してはならないかを指示するプロパティです。
その挙動は、先ほどのサンプルによく似ています。
'clear'プロパティの仕様
'none'以外の値は、潜在的にクリアランスを導入する。クリアランスはマージンの相殺を抑制し、要素のmargin-topの上へ空白のように振る舞う。浮動体を過ぎて垂直に要素を押すために使用される。
'clear'が設定された要素のクリアランスの計算は最初に要素の上ボーダー辺の仮の決定によってされる。要素の'clear'プロパティが'none'である場合、この位置は実際の上ボーダー辺となる。
9.5.2 浮動体に隣接するフローの制御: 'clear'プロパティ - W3C 視覚整形モデル (邦訳)
clearは、marginの相殺を抑制し、floatの底辺に要素がつくように、margin-topの値を自動調整するような振る舞いを行います。
clear:none;の場合は、通常通りに振舞います。
以下、margin, float, clearの例です。
<style>
img {
float:left;
}
.right {
float:right;
width:80px;
height:240px;
margin-bottom:10px;
}
.clear {
margin-top:3000px;
clear:left;
}
.clear-right {
margin-top:-1000px;
clear:right;
}
</style>
<img src="dummy.jpg" alt="" width="80" height="80" />
<img src="dummy.jpg" alt="" width="80" height="80" />
<img src="dummy.jpg" alt="" width="80" height="80" />
<div class="right"></div>
<div class="clear">clear:left;</div>
<div class="clear-right">clear:right;</div>
.clearにmargin-topに3000pxも指定しています。
本来なら、3000pxの間隔が空くはずですが、float:leftを指定したimgの底辺にくっついてしまっています。
これはclearによる仕様です。
clear:left;を指定したことによって、margin-topが自動調整されて、float:left;の指定された要素の後ろにつきます。
さらに、float:right;を指定した.rightの底辺(marginを含む)には、.clear-rightの上辺が重なっています。
この場合、.rightの上辺から底辺までのmargin-topを調整している訳ではありません。
.clearから.rightの底辺までの距離で、margin-topを調整しています。
なぜなら、隣接するfloatを指定した要素とmarginは、相殺関係になりますが、.clearのように隣接する要素とmarginに相殺関係が発生しない場合は、そこからが出発点になります。
clearとmargin-topを同時に適用すると、clearプロパティが優先されるので気をつけて下さい。
相殺の利点
うんざりしてくるほど、ややこしいmarginですが、間隔を適切にあけたい場合。
相殺など利用することにより、クリーンなソースコードが書ける場合も多くあります。
レイアウトをmarginで書いたものと、paddingで書いたものを比較します。
完成するレイアウト
margin
<style type="text/css">
dl {
margin:3em 0;
}
dt {
display:inline;
float:left;
}
dd {
margin:1em 0 3em 120px;
}
p {
margin:1em 0;
}
</style>
<dl>
<dt>margin デモ1</dt>
<dd>ddタグの中に直接テキストを入れています。</dd>
<dt>margin デモ2</dt>
<dd>
<p>ddタグの中にpタグを入れています。</p>
<p>ddとpのmarginが重なるので、相殺が起きます。</p>
</dd>
<dt>margin デモ3</dt>
<dd>
<img src="dummy.jpg" alt="" width="480" height="32">
<p>画像が入っても適度に間隔があきます。</p>
<p>また下に入っても問題ありません。</p>
<img src="dummy.jpg" alt="" width="480" height="32">
<p>さら下に入っても問題ありません。</p>
</dd>
</dl>
padding
<style type="text/css">
/* CSSリセット */
dl, dd, p {
margin:0;
}
dl {
padding:3em 0;
}
dt {
display:inline;
float:left;
}
dd {
padding:0 0 3em 120px;
}
.pt {
padding-top:1em;
}
.pb {
padding-bottom:1em;
}
.last-child {
padding-bottom:0;
}
</style>
<dl>
<dt>margin デモ1</dt>
<dd>ddタグの中に直接テキストを入れています。</dd>
<dt>margin デモ2</dt>
<dd>
<p class="pb">ddタグの中にpタグを入れています。</p>
<p>pタグ同士の間隔をあけるので、classを入れなければいけません。</p>
</dd>
<dt>margin デモ3</dt>
<dd class="last-child">
<img src="dummy.jpg" alt="" width="480" height="32">
<p class="pt pb">画像が入ると、class祭りになります。</p>
<p class="pb">新しくpタグを入れようと思うと、classを更に入れなければなりません。</p>
<img src="dummy.jpg" alt="" width="480" height="232">
<p class="pt">classがやたら増える。</p>
</dd>
</dl>
marginの相殺を利用したものと、paddingを使ってレイアウトを組んだものの比較です。
paddingを使用すると、最後の要素は、padding-bottom:0; を指定したり、classを細かく割り振ったりします。
.mt10, .mt20などのクラスを用意している人は、paddingのような指定を行っていることが多いです。
それに対して、marginの相殺を利用すると、HTML, CSSともに、短く書くことができます。
さらに適切に間隔を保つことができるので、特にCSSを指定していないimg要素がイレギュラーに入ったとしても、ある程度は容易に対処することができます。
上記のサンプルは、相殺をうまく利用した例です。
デザインによっては、親にborderやpaddingなどを入ることにより、相殺が使えない場合もあります。
しかし、相殺を理解していることで、HTMLやCSSの書く効率が変わってきます。
ブラウザのデフォルトスタイルシート
ブラウザのデフォルトスタイルシートは、marginと関連があります。
h1やpなどの要素には、ブラウザで、あらかじめCSSがついています。
h1 {
margin:0.67em 0;
font-size:2em
}
p {
margin:1em 0;
}
相殺を計算されたうえで、上下にmarginの指定されています。
このスタイルシートによって、"文章"を適切にマークアップすれば、適度な間隔を保つようになっています。
デフォルトスタイルシートを生かすように構築する場合は、相殺の考え方が大切になります。
marginの仕様とバグは、度々一緒にされることがあります。
marginは、奥深く、経験もある程度必要になります。
marginのバグも回避策は多くあるので、マスターすればCSSの構築に役立ってくれるプロパティになるはずです。