2.IDEA

JP only

リキッドレイアウトのCSSをマスターする(+レスポンシブウェブデザインへの適用)

レスポンシブウェブデザインを制作するときによく使うレイアウト手法である「リキッドレイアウト」のCSSテクニックについて、まとまった記事がなかったので書きます。今回は、JavaScriptを使わないネイティブCSSでの実装を紹介します。

目次

  1. 高さを揃える必要がない場合
    1. 余白なしの場合のCSS
    2. 余白が可変の場合のCSS
      1. marginを使うパターン
      2. paddingを使うパターン
    3. 余白が固定の場合のCSS
      1. marginを使うパターン
      2. paddingを使うパターン
  2. 高さを揃える必要がある場合
    1. background: linear-gradientの利用
    2. 擬似要素:before, :afterの利用
    3. CSS Table(もしくはtableタグ)の利用
    4. One true layoutの利用
    5. CSS3 flexboxの利用
  3. レスポンシブウェブデザインに適用してみる
    1. 高さを揃えなくて良いケース
      1. box-sizing:border-boxを使う(余白が固定幅)
      2. ネガティブマージンをつかう(余白が固定幅)
    2. 高さを揃えるケース
      1. One True Layoutを使う
      2. flexboxを使う(flexboxが使えないブラウザにはtableを使う)
      3. JavaScriptを使う(おまけ)
  4. マルチデバイス対応にリキッドレイアウトは必須か?
  5. 参考

1. 高さを揃える必要がない場合

デモが一括で見れるページはこちらです

カラムの高さが同じだと保証できる場合は、こちらを使いましょう。サイズが同じ写真を並べるギャラリーとかですかね。
高さが異なる場合でも、カラムが背景と地続きなら高さを揃える必要がないため、使えます(このサイトのギャラリーページのようなケースです)。

1.1. 余白なしの場合のCSS

余白無しの均等幅リキッドレイアウトのイメージ画像
<div class="row">
  <div class="cell">one</div>
  <div class="cell">two</div>
</div>
.cell{
  float: left;
  width: 50%;
}

余白なしの場合のCSSのデモ

簡単ですね。width:(100÷カラム数)%で大丈夫です。IE7以下ではパーセント計算のバグ(詳細はこちらの記事)があるので、若干値を小さくしましょう。

1.2. 余白が可変の場合のCSS

余白が可変の均等幅リキッドレイアウトのイメージ画像

カラムの「幅」にパーセント指定、「余白」にもパーセント指定をするパターンです。marginを使うパターン、paddingを使うパターンがあります。

1.2.1. marginを使うパターン

2カラムであれば単純に、widthとmarginで合計100%になるように割り振ると良いです。

<div class="row">
  <div class="one">one</div>
  <div class="two">two</div>
</div>
.one{
  float: left;
  width: 48%;
  margin: 0 2% 0 0;
}
.two{
  float: left;
  width: 48%;
  margin: 0 0 0 2%;
}

余白が可変でmarginを使う2カラムの場合のデモ

3カラム以上になると一つひとつ指定するのが面倒くさいので、親要素にネガティブマージンを使ってスマートにスタイルしてみます(もちろん2カラムでも同じことができます)。

<div class="row">
  <div class="cell">one</div>
  <div class="cell">two</div>
  <div class="cell">three</div>
</div>
.row{
  margin-right: -1.33%;
}
.cell{
  float: left;
  width: 32%;
  margin-right: 1.33%;
  /* 32 + 1.33 = 33.33% = 3分の1 */
}

余白が可変でmarginを使う3カラムの場合のデモ

1.2.2. paddingを使うパターン

paddingを使う場合も、2カラムであればmarginと同じように書けます。
ただ当たり前ですが、背景色を指定している場合、paddingではカラム間に余白ができないので、子要素を追加してそちらに背景色を指定します。

<div class="row">
  <div class="one"><div>one</div></div>
  <div class="two"><div>two</div></div>
</div>
.one{
  float: left;
  width: 48%;
  padding: 0 2% 0 0;
}
.two{
  float: left;
  width: 48%;
  padding: 0 0 0 2%;
}
.one div, .two div{
  background-color: #f6f6f6;
}

余白が可変でpaddingを使う2カラムの場合のデモ

3カラム以上では、marginと同じやり方(親要素にネガティブマージン)でやってしまうと、ネガティブマージンの分だけ右にはみ出してしまいます。
なので、box-sizing: border-boxを使うのが良いです。こうすると、要素のpaddingやborderまで含めた幅をwidthで指定できるようになります。

カラム間にborderを挟みたい場合もborder-boxを使います。
ただ、border-boxはIE7以下でサポートされていません。

<div class="row">
  <div class="cell first"><div>one</div></div>
  <div class="cell"><div>two</div></div>
  <div class="cell"><div>three</div></div>
</div>
.cell{
  float: left;
  width: 33.33%;
  box-sizing: border-box;
  padding: 0 1%;
  border-left: 1px solid #ededed;
}
.cell.first{
  border-left: none;
}
.cell div{
  background-color: #f6f6f6;
}

余白が可変でpaddingを使う3カラムの場合のデモ

1.3. 余白が固定の場合のCSS

余白が固定の均等幅リキッドレイアウトのイメージ画像

カラムの「幅」にパーセント指定、「余白」には固定幅(pxなど)を指定するパターンです。余白が可変だと親要素の幅が広いとき不格好に見えるので、個人的にはこちらのほうが実用的だと思っています。こちらもmarginのパターンとpaddingのパターンがあります。

1.3.1. marginを使うパターン

こちらでは、少し高度(?)なテクニックを使います。
2カラムを例にとると、両カラムともwidth:50%にして余白(margin)をいくらかとると全体で100%を超えてレイアウトが崩れてしまいます。
だからといって、固定幅の余白だと、親の幅によってその割合は変わるので、適当にwidth:48%などと指定することもできません。

そこで、余白分だけ右カラムのmargin-rightに負の値を指定します。そうすると、一行に収まる代わりに、右カラムが右にはみ出します。そのはみ出した分はどうするかというと、親要素に同じ値のpadding-rightを指定すると、綺麗に収まってくれます。

<div class="row">
  <div class="one">one</div>
  <div class="two">two</div>
</div>
.row{
  padding-right: 20px;
}
.one{
  float: left;
  width: 50%;
  margin-right: 20px;
}
.two{
  float: left;
  width: 50%;
  margin-right: -20px;
}

余白が固定でmarginを使う2カラムの場合のデモ

3カラムでも、余白の合計を「ネガティブマージン+親要素パディング」に指定すれば、同じようにできます。

<div class="row">
  <div class="cell">one</div>
  <div class="cell">two</div>
  <div class="cell last">three</div>
</div>
.cell{
  float: left;
  width: 33.33%;
  margin-right: 20px;
}
.cell.last{
  // 20px + 20px
  margin-right: -40px;
}
.row{
  padding-right: 40px;
}

余白が固定でmarginを使う3カラムの場合のデモ

1.3.2. paddingを使うパターン

marginと同じようにできます。

<div class="row">
  <div class="one"><div>one<div></div>
  <div class="two"><div>two<div></div>
</div>
.row{
  padding-right: 20px;
}
.one{
  float: left;
  width: 50%;
  padding-right: 10px;
}
.two{
  float: left;
  width: 50%;
  margin-right: -20px;
  padding-left: 10px;
}
.one div, .two div{
  background: #f6f6f6;
}

余白が固定でpaddingを使う2カラムの場合のデモ

こちらもボーダーを使用する際は、box-sizing: border-boxを使用します。

<div class="row">
  <div class="cell first"><div>one</div></div>
  <div class="cell"><div>two</div></div>
  <div class="cell"><div>three</div></div>
</div>
.cell{
  float: left;
  width: 33.33%;
  box-sizing: border-box;
  padding: 0 10px;
  border-left: 1px solid #ededed;
}
.cell.first{
  border-left: none;
}
.cell div{
  background-color: #f6f6f6;
}

余白が固定でpaddingを使う3カラムの場合のデモ

2. 高さを揃える必要がある場合

カラム間の高さを揃えるレイアウトのイメージ画像

カラムの高さが決定していないかつ、カラムに背景色や枠線がついていて背景と地続きでない場合は、カラム間の高さを揃える必要があるかと思います。
単に、各カラムにheight:100%すれば良いと思うかもしれませんが、親要素にheightを指定していない場合、height:100%はheight:autoになってしまいます

そこで、高さを揃えるためのCSSを考えないといけないんですが、実はCSS-TRICKSで5つの手法が紹介されています。ここでは、その概要を紹介するに留めておきます。

一括デモはこちら(CSS-TRICKSサイト内)

※説明の中で使う言葉は以下のような構造を想定しています。

<div class="container">
  <div class="column">one</div>
  <div class="column">two</div>
  <div class="column">three</div>
</div>

2.1. background:linear-gradientの利用

カラム自体の配置は普通にfloat:left; width:20%(5カラムの場合);で行います。
しかし、高さが揃っていないため、カラムには背景色を指定できません。そこで、親のコンテナに背景色を指定します。
linear-gradientで20%ごとに階段状に色を変えてカラムが5個あるように見せます。途中に1%の白をはさんで余白をつくる、なんてこともできます。
CSS3 gradientを使用しているため、モダンブラウザに限定されます。

Doug Neinerの手法のイメージ画像

2.2. 擬似要素の利用

カラムの配置は、上記の方法と同様floatを使いますが、背景色は擬似要素の:before, :afterを使います。
親のコンテナにrelative指定、:before, :afterにabsolute指定して、それぞれのカラムに配置します。1カラム目=親コンテナの背景色、2カラム目=:beforeの背景色、3カラム目=:afterの背景色といった具合です。
以下:beforeの指定例です。

.container:before{
  background: #ccc;
  content: "";
  position: absolute;
  /*絶対配置であれば、親の高さに指定がなくてもheight:100%が使えます。*/
  height: 100%;
  top: 0;
  left: 33.4%;
  width: 33.4%;
  z-index: -1;
}
Nicolas Gallagherの手法のイメージ画像

こちらはCSS2なのでモダンブラウザじゃなくても使えます(まあこれ、3カラムにしか使えないですが...)。

2.3. CSS Table(もしくはtableタグ)の利用

なんと、tableを使えば自動的に高さを揃えてくれます!CSSもとてもシンプルで、paddingも使い放題です。
時代遅れと蔑まれたtableですが、最近見直されているように思います。
PixelGridさんのCodeGridでも、その実用性を紹介されています。

.container {
  display: table;
}
.container .column {
  display: table-cell;
  width: 25%;
  padding: 10px;
}

CSS-TRICKSでは、tableのデメリットをsource-order dependence(表示順がHTMLを書いた順番に依存してしまう)と書いてあります。たしかに他の方法に比べると柔軟性には欠けますが、逆方向にするだけだったら、direction: rtl;で可能です(これもCodeGridに書いてあったことです(ステマくさい))。

2.4. One true layout の利用

One true layoutはかなり昔からある手法のようです。こちらもカラムの配置はfloatで行いますが、それだけでは高さが揃ってくれません。そこで、以下のようなCSSを適用します。

.container{
    overflow: hidden;
}
.container .column {
    float: left;
    margin-bottom: -99999px;
    padding-bottom: 99999px;
    width: 33.3%;
}

これだけ見ても謎だと思うので、補足します。

ある要素が以下3つのどれかに該当し、子要素にブロック要素を持っている場合、その高さは、一番上の子要素の上マージン辺から、一番下の子要素の下マージン辺までの距離で計算されるようです。

  1. overflowがvisibleではないブロック要素
  2. インラインブロック要素
  3. floatされた要素

以下、引用です。

10.6.6 Complicated cases

This section applies to:

  • Block-level, non-replaced elements in normal flow when 'overflow' does not compute to 'visible' (except if the 'overflow' property's value has been propagated to the viewport).
  • 'Inline-block', non-replaced elements.
  • Floating, non-replaced elements.

If, or are 'auto', their used value is 0. If is 'auto', the height depends on the element's descendants per 10.6.7.

(中略)

10.6.7 'Auto' heights for block formatting context roots

(中略)

If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level child box and the bottom margin-edge of the bottommost block-level child box.

引用元: Visual formatting model details - 10.6.6 Complicated cases

ここでのポイントは、負の値のmarginもそのまま計算に使用されるということです。

こちらのデモで確認できます。

ここで先ほどのOne true layoutのCSSを見てみると、パディングでそれぞれのカラムの高さをひたすら長くして、ネガティブマージンで親要素の高さを元に戻しているのがわかるかと思います。

One true layoutのイメージ画像

つまり、overflow:hiddenで隠しているだけで、カラムたちは99999px以上の高さを持っているため、パフォーマンスやSEOに影響しかねません。なので、目分量で500pxとかにすると良いと思います。
また、カラム内でidを使ったとき、アンカーリンクなどでそのidへ移動すると、そのidより上のコンテンツが隠れてしまうバグがあるので、実用の際は注意する必要があります。

2.5. CSS3 flexboxの利用

モダンブラウザに限定するのであれば、CSS3を利用して簡単に実装出来ます。

.container {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-around;
  align-items: stretch;
}
.container .column {
  background: #f2f2f2;
  padding: 20px;
  width: 600px;
}

3. レスポンシブウェブデザインに適用してみる

色々な方法を紹介してきましたが、実際にレスポンシブウェブデザインに使ってみましょう。PCで4カラム、タブレットで2カラム、スマホで1カラムと変化するレイアウトを作ってみます。

実用で使うことを第一に考えて、簡単さ、柔軟さ、網羅性、仕様への忠実さなどを加味したおすすめ順に紹介します。

3.1. 高さを揃えなくて良いケース

今まで見てきたレスポンシブなサイトを思い浮かべる限り、高さを揃えずに対応出来るケースが多かったように思います。

3.1.1. box-sizing:border-boxを使う(余白が固定幅)

<div class="liquid">
    <div class="liquid-col"><div class="liquid-col-inner">...</div></div>
    <div class="liquid-col"><div class="liquid-col-inner">...</div></div>
    <div class="liquid-col"><div class="liquid-col-inner">...</div></div>
    <div class="liquid-col"><div class="liquid-col-inner">...</div></div>
</div>
.liquid{
  padding: 10px;
}
.liquid-col{
  padding: 10px;
  box-sizing: border-box;
  float: left;
  width: 25%;
}
.liquid-col-inner{
  background: #f3f3f3;
}
@media screen and (max-width: 768px) and (min-width: 481px){
  .liquid-col{
    width: 50%;
  }
}
@media screen and (max-width: 480px){
  .liquid-col{
    width: 100%;
  }
}

border-boxを使うレスポンシブウェブデザインのデモ

とてもシンプルで直感的なCSSで実装出来ます。パーセントへの乗り換えも簡単です。
IE7以下には使えませんが、そもそもメディアクエリーが使えないIE8以下は、サイトの幅を固定にすることが多いかと思います。
そのときは、IE用のCSSを用意してpx指定でスタイルしてやれば良いかと思います。

3.1.2. ネガティブマージンをつかう(余白が固定幅)

<div class="liquid">
    <div class="liquid-col">...</div>
    <div class="liquid-col liquid-col--two">...</div>
    <div class="liquid-col">...</div>
    <div class="liquid-col liquid-col--four liquid-col--two">...</div>
</div>
.liquid{
  padding-right: 60px;
}
.liquid-col{
  margin: 0 20px 20px 0;
  background: #f2f2f2;
  float: left;
  width: 25%;
  *width: 24.9%;
}
.liquid-col--four{
  margin-right: -60px;
}
@media screen and (max-width: 768px) and (min-width: 481px){
  .liquid{
    padding-right: 20px;
  }
  .liquid-col{
    width: 50%;
  }
  .liquid-col--two{
    margin-right: -20px;
  }
}
@media screen and (max-width: 480px){
  .liquid{
    padding-right: 0;
  }
  .liquid-col{
    margin-right: 0;
    width: 100%;
  }
}

ネガティブマージンを使うレスポンシブウェブデザインのデモ

box-sizingのやり方よりシンプルではなくなりましたが、こちらにも利点はあります。メンテナンス性です。なぜなら、IE7でも正しく表示されるので、IE用のCSSを用意する必要がないからです。

また、この方法はリキッドレイアウト以外でも、「○○px余白を空けて△:□の比の幅で配置したい」というレイアウトなら親の幅に関係なく使い回すことができる、なかなか便利な代物です。

3.2. 高さを揃えるケース

こちらはレスポンシブに使える手法となると、「linear-gradientを使う方法」と「擬似要素を使う方法」が消えます。

3.2.1 One True Layoutを使う

こちらはHTMLやCSSが少し長いので、ソースはデモページから見てください。

One True Layoutを使うレスポンシブウェブデザインのデモ

さきほどの「3.1.2. ネガティブマージンを使う」にOne True Layoutを適用しただけです。
カラム内でアンカーリンク先となるidを使わないならば、最もシンプルに書くことができるため、One True Layoutをおすすめします。

3.2.2 flexboxを使う(flexboxが使えないブラウザにはtableを使う)

カラム内に「リンク先として使用したいid」がある場合、もしくは、どうしてもCSSハック的なことをしたくない人は、こちらかと思います。こちらも長いので、ソースはデモページから見てください。

flexboxとtableを使うレスポンシブウェブデザインのデモ

One True Layoutに比べて、結構面倒くさいです。
というのも、以下のようなことがあるからです。

  • flexboxが使えないブラウザを判別する必要がある
  • flexboxが使えないブラウザにはtableを使う必要がある
  • display:tableはIE8までしか対応していないから、IE7以下は個別のCSSをあてる必要がある
  • tableでは、メディアクエリーによるwidth等の切り替えだけではレスポンシブにできない
  • そのため、4カラム用と2カラム用のdivを書いてdisplay:noneで切り替える必要がある
  • しかし、それを単純にHTMLに書くと、コンテンツが重複していることになるのでSEO的に良くない

面倒くさいですね...以下のようにコーディングします。

  • HTML内:4カラム用のdivコンテナだけ書く
  • tableのCSS:4カラム用のdivコンテナ、2カラム用のdivコンテナにスタイルをあて、メディアクエリーで表示/非表示を切り替えるようにする
  • flexboxのCSS:4カラム用のdivコンテナにスタイルをあてる
  • Modernizr:flexboxの使用可/不可を判別
  • JavaScript:flexboxが使えないと判別されたブラウザには、2カラム用のdivコンテナをDOM操作で追加する
  • Googlebot対策:↑の条件式で、ユーザーエージェントがGooglebotだと2カラム用のdivコンテナを追加しないようにする

正直、レスポンシブでカラム内に「リンク先として使用するid」を入れるケースなんて稀だと思うので、こんな面倒くさいことせずにOne true layoutを使うべきかなと思います。

3.2.3 JavaScriptを使う(おまけ)

3.2.2. の方法を使うくらいだったら、もうJavaScriptで高さを取得した方が良いと思います。

JavaScriptを使うレスポンシブウェブデザインのデモ

まず、各カラムのheightを100%にします。そして、一番大きいカラム(の子要素)の高さを親コンテナのheightに適用する関数を、onloadとresizeに登録すればOKかと思います。(スクロールしないで見える箇所にある場合は、高さが変わる様子が見えてしまうので、処理が終わってからコンテンツを表示する必要があります)

4. マルチデバイス対応にリキッドレイアウトは必須か?

さんざん紹介してきましたが、使う前にそもそも必要か考えた方が良いかと思います。

リキッドレイアウトを使用したレスポンシブウェブデザインは、1つのソースでどんなデバイスにも対応出来る優れものです。
その一方で、その柔軟さ故のデメリットも存在します。

  • すべてのデバイスに対して画像が最適化できない(拡大or縮小される)
  • 「余白」や「ブロックの縦横比」などの変化による、デザイン性(見た目)の低下

DTP経験者やピクセルパーフェクトでやってきた人たちなんかは、「ここにこのサイズの画像を当てはめて...」「このブロックの縦横比は黄金比で...」と、コーディング前の段階で「洗練された見た目」が完成します。リキッドレイアウトにしてしまうと、その絶妙なバランスを崩すことになります。

つまり、リキッドレイアウトを使った時点で「洗練された見た目」をいくらか放棄する、ということを心に留めておく必要があります。

実は、それを解決する折衷案として、「メディアクエリーを使って、デバイスごとに最適なレイアウトを固定幅で組む」やり方なんかもあります。

以下は、コンテナの幅をメディアクエリーで変える(だけの)デモです。

コンテナの幅をメディアクエリーで変える(だけの)デモ
追記:わかりやすい例としてgood design companyのウェブサイトがありました。

手間はかかりますが、「洗練された見た目」はある程度維持出来ます。

いずれにせよ、プロジェクトの上位目的から、サイトに求められることをしっかり考え、適切な手段を選ぶ必要がありますね。

高さを揃えるケースとレスポンシブへの適応を追加

5. 参考