レスポンシブなサイトで高さが可変のブロック要素の一覧をレイアウトする際にてこずったので、課題になったこととその解決方をメモしておきます。ニュースサイトなどに良くあるサムネイル画像とタイトルのブロック要素の一覧で、画面サイズに合わせてカラム数が変わる以下のようなレイアウトです。ちなみに、Flexboxを使わないやり方です。nth-child()とclearを使った方法、inline-blockを使った方法、さらに、flexboxを使った方法の3つをまとめてみました。
レイアウトの条件
レイアウトの条件を整理しておきます。
- 表示件数が変わる
 - 表示件数が変わる場合でも同じHTMLとCSSでレイアウトする
 - ブロックによって高さが異なる
 - 画面サイズに合わせてカラム数を変える
 
上の条件だとli要素をfloatしてメディア・クエリで画面ごとに幅を指定すれば簡単にできそうです。ところが、ブロックの高さが変わるのでそう簡単にはいきません。
特定の画面サイズでレイアウトが崩れてしまう
例えば、大きい画面では3カラム、中くらいの画面では2カラム、小さい画面では1カラムにするレイアウトで、li要素をfloatを使ってスタイルすると、特定の画面サイズでレイアウトが崩れてしまいます。しかも、下のイメージのように3カラム表示の際にレイアウトが崩れる部分と2カラム表示の際に崩れる部分が異なります。
3カラム表示
4番目のブロックが2番目のブロックに引っかかってレイアウトが崩れてしまう。
2カラム表示
今度は7番目のブロックが5番目のブロックに引っかかってレイアウトが崩れてしまう。本来意図したレイアウトでは、7番目のブロックが左寄せになります。
HTMLとCSSの例
上のようなレイアウトの崩れは、以下のようなHTMLとCSSの記述で起こります。
HTML
li要素の中にサムネイル画像と文字数の違う記事のタイトルが入っています。
<ul class="block-grid block-grid-1-2-3">
  <li>
    <img src="http://placehold.it/600x360?text=1" />
    <a href="">記事のタイトルなどのテキスト要素</a>
  </li>
  <li>
    <img src="http://placehold.it/600x360?text=2" />
    <a href="">記事のタイトルなどのテキスト要素なので、画面の幅によって改行が生じます</a>
  </li>
  <li>
    <img src="http://placehold.it/600x360?text=3" />
    <a href="">記事のタイトルなどのテキスト要素</a>
  </li>
  ...
</ul>
CSS
以下の例では、〜600pxまでの画面サイズでは1カラム、600〜800pxの画面サイズでは2カラム、800px以上の画面サイズでは3カラムで表示するように、メディア・クエリを使って各画面サイズのスタイルでli要素に幅を指定しています。
.block-grid {
  width: auto;
  overflow: hidden;
  list-style: none;
  margin: 0 -1% 30px;
  padding: 0;
}
.block-grid li {
  float: left;
  margin: 0 1% 30px;
  padding: 0;
}
.block-grid a {
  text-decoration: none;
  font-size: 1.6em;
}
@media (min-width: 600px){
  .block-grid-1-2-3 li {
    width: 48%;
  }
}
@media (min-width: 800px){
  .block-grid-1-2-3 li {
    width: 31.3333%;
  }
}
Advertisement
nth-child()セレクタとclearプロパティを使う方法
3カラムと2カラムの表示スタイルを指定しているメディア・クエリ部分に、以下を追加するだけで対応ができます。以下のスタイルを追加することで、各カラムの最初の要素にclear: bothを指定して回り込みを解除しています。
600px以上の画面用のスタイルに追加
600〜800pxでは2カラムで表示しているので、それぞれのカラムの1つ目のブロックになる1、3、5、7番目の要素にnth-child(2n+1)を使ってclear: bothを指定します。
.block-grid-1-2-3 li:nth-child(2n+1) {
  clear: both;
}
800px以上の画面用のスタイルに追加
800px以上の画面では3カラムで表示しているので、それぞれのカラムの1つ目のブロックになる1、4、7番目の要素にnth-child(3n+1)を使ってclear: bothを指定します。そして、600〜800pxの画面様に指定した回り込み解除を無効にするためにnth-child(2n+1)にclear: noneを指定します。この際、2と3の公倍数で回り込みの解除を無効にしないようにnth-child(3n+1)の方を後に記述します。
.block-grid-1-2-3 li:nth-child(2n+1) {
  clear: none;
}
.block-grid-1-2-3 li:nth-child(3n+1) {
  clear: both;
}
メディア・クエリ部分の全CSS
最終的に、メディア・クエリ部分のCSSは以下のようになります。
@media (min-width: 600px){
  .block-grid-1-2-3 li {
    width: 48%;
  }
  .block-grid-1-2-3 li:nth-child(2n+1) {
    clear: both;
  }
}
@media (min-width: 800px){
  .block-grid-1-2-3 li {
    width: 31.3333%;
  }
  .block-grid-1-2-3 li:nth-child(2n+1) {
    clear: none;
  }
  .block-grid-1-2-3 li:nth-child(3n+1) {
    clear: both;
  }
}
これで、どの画面サイズでもレイアウトが崩れないようになります。
nth-child()のブラウザサポートについて
nth-child()セレクタは、IE9以上とモダンブラウザではサポート  されています。IE8以下で対応が必要な場合は、JavaScriptなどで対応  する必要があります。そもそも、IE8ではメディア・クエリもサポートされてないですし、対応する状況はあまりないかもしれませんが。
inline-blockを使う方法
[追記: 2016/05/17 14:00]
Twitterで以下のようなコメントをいただいたので、試してみました。
これならinline-blockでも良いかも…? | "表示件数と高さが変わるブロックの一覧を、画面サイズに合わせてカラム数を変えて表示する方法 – Rriver" https://t.co/y74wpnncRK
— riatw (@riatw) May 17, 2016
inline-blockを使ったやり方のほうがいいかも?
以下のようにli要素の間のスペースを削除しないとレイアウトが崩れてしまいますが、それを除けばinline-blockを使った方法のほうがシンプルでいいかもしれません。(まだブラウザでしっかり検証できてないですが。。。)
以下のように、コメントアウトをするなどしてli要素間のスペースを削除しないとレイアウトが崩れてしまうのでご注意を。
<ul class="block-grid block-grid-1-2-3">
  <li>
    <img src="http://placehold.it/600x360?text=1" />
    <a href="">記事のタイトルなどのテキスト要素
  </li><!--
  --><li>
    <img src="http://placehold.it/600x360?text=2" />
    <a href="">記事のタイトルなどのテキスト要素なので、画面の幅によって改行が生じます</a>
  </li><!--
  --><li>
    <img src="http://placehold.it/600x360?text=3" />
    <a href="">記事のタイトルなどのテキスト要素</a>
  </li><!--
  --><li>
  ...
</ul>
CSSは.block-grid liに指定していたfloat: left;をdisplay: inline-block;に入れ替えて、vertical-align: top;を追加しました。
.block-grid li {
  display: inline-block;
  vertical-align: top;
  margin: 0 1% 30px;
  padding: 0;
}
@riatwさん 、ありがとうございました!
inline-blockの隙間を無くす2つの方法
上でコメントアウトで消したli要素間のスペースは、親要素のfont-sizeやletter-spacingを使って消すことも可能です。
font-sizeを使う場合
以下のように親要素にfont-size: 0;を入れてli要素にfont-size: 16px;を入れることでli要素間の隙間をなくすことができます。この際、font-sizeはemや%の相対値ではli内の文字が表示されなくなるため、pxなどの絶対値を記述する必要があります。
.block-grid {
  width: auto;
  overflow: hidden;
  list-style: none;
  margin: 0 -1% 30px;
  padding: 0;
  font-size: 0;
}.
block-grid li {
  display: inline-block;
  vertical-align: top;
  margin: 0 1% 30px;
  padding: 0;
  font-size: 16px;
}
letter-spacingを使う場合
以下のように親要素にletter-spacing: -0.4em;を入れてli要素にletter-spacing: normal;を入れることでli要素間の隙間をなくすことができます。
.block-grid {
  width: auto;
  overflow: hidden;
  list-style: none;
  margin: 0 -1% 30px;
  padding: 0;
  letter-spacing: -0.4em;
}
.block-grid li {
  display: inline-block;
  vertical-align: top;
  margin: 0 1% 30px;
  padding: 0;
  letter-spacing: normal;
}
Flexboxならもっと簡単?
[追記: 2016/05/17 23:00]
せっかくなので、Flexbox版も試しに作ってみました。自動でブロックの高さが同じになるので、各ブロックに背景やボーダーを入れたい場合などはFlexboxを使うとめちゃくちゃ楽できそうです。IEのバージョンを気にしなくてすむならFlexbox版が一番いいかもしれませんね。
ただ、Flexboxはまだ実戦で使ったことがなくて、個人的にはもう少しフォールバック対応を検証したりテストが必要だと思っているのですが、使えるようになったら便利ですね〜。
CSS
親要素になる.block-gridにdisplay: flex;とflex-wrap: wrap;を追加してul要素をFlexboxで表示するように指定します。また、子要素の.block-grid liにflex: 0 1 98%;を追加して、子要素の表示方法を指定します。この部分をメディア・クエリで画面幅に合わせて変えていきます。
以下、必要なCSSです。
.block-grid {
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  margin: 0 -1% 30px;
  padding: 0;
}
.block-grid li {
  flex: 0 1 98%;
  margin: 0 1% 30px;
  padding: 0;
  background: pink;
}
.block-grid a {
  text-decoration: none;
  font-size: 1.6em;
}
@media (min-width: 600px){
  .block-grid-1-2-3 li {
    flex: 0 1 48%;
  }
}
@media (min-width: 800px){
  .block-grid-1-2-3 li {
    flex: 0 1 31.3333%;
  }
}
もっと良いやり方をご存知でしたら、ぜひ、この記事のコメントやTwitter などでコメントいただければ幸いです。
ちなみに、Flexboxを使った方法について、詳しくはこの記事(英語) あたりが参考になりそうです(手抜きですみません。。。)。
2016年5月16日に公開され、2016年10月8日に更新された記事です。
About the author
                  「明日のウェブ制作に役立つアイディア」をテーマにこのブログを書いています。アメリカの大学を卒業後、ボストン近郊のウェブ制作会社に勤務。帰国後、東京のウェブ制作会社に勤務した後、ウェブ担当者として日英バイリンガルのサイト運営に携わる。詳しくはこちら。
ウェブ制作・ディレクション、ビデオを含むコンテンツ制作のお手伝い、執筆・翻訳のご依頼など、お気軽にご相談ください。いずれも日本語と英語で対応可能です。まずは、Mastodon @rriver@vivaldi.net 、Twitter @rriver 、またはFacebook までご連絡ください。
[…] 表示件数と高さが変わるブロックの一覧を、画面サイズに合わせてカラム数を変えて表示する方法 […]
inline-blockを指定したときは、親要素ulにfont-size:0を指定すると崩れなくなりますよー。
liにはフォントサイズを指定します