表示件数と高さが変わるブロックの一覧を、画面サイズに合わせてカラム数を変えて表示する方法

Advertisement

レスポンシブなサイトで高さが可変のブロック要素の一覧をレイアウトする際にてこずったので、課題になったこととその解決方をメモしておきます。ニュースサイトなどに良くあるサムネイル画像とタイトルのブロック要素の一覧で、画面サイズに合わせてカラム数が変わる以下のようなレイアウトです。ちなみに、Flexboxを使わないやり方です。nth-child()clearを使った方法、inline-blockを使った方法、さらに、flexboxを使った方法の3つをまとめてみました。

block-grid-layout@2x

レイアウトの条件

レイアウトの条件を整理しておきます。

  1. 表示件数が変わる
  2. 表示件数が変わる場合でも同じHTMLとCSSでレイアウトする
  3. ブロックによって高さが異なる
  4. 画面サイズに合わせてカラム数を変える

上の条件だとli要素をfloatしてメディア・クエリで画面ごとに幅を指定すれば簡単にできそうです。ところが、ブロックの高さが変わるのでそう簡単にはいきません。

特定の画面サイズでレイアウトが崩れてしまう

例えば、大きい画面では3カラム、中くらいの画面では2カラム、小さい画面では1カラムにするレイアウトで、li要素をfloatを使ってスタイルすると、特定の画面サイズでレイアウトが崩れてしまいます。しかも、下のイメージのように3カラム表示の際にレイアウトが崩れる部分と2カラム表示の際に崩れる部分が異なります。

3カラム表示

4番目のブロックが2番目のブロックに引っかかってレイアウトが崩れてしまう。

block-grid-three-column-broken

2カラム表示

今度は7番目のブロックが5番目のブロックに引っかかってレイアウトが崩れてしまう。本来意図したレイアウトでは、7番目のブロックが左寄せになります。

block-grid-two-column-broken

デモはこちら

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を使ったやり方のほうがいいかも?

以下のように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-sizeletter-spacingを使って消すことも可能です。

font-sizeを使う場合

以下のように親要素にfont-size: 0;を入れてli要素にfont-size: 16px;を入れることでli要素間の隙間をなくすことができます。この際、font-sizeem%の相対値では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-griddisplay: flex;flex-wrap: wrap;を追加してul要素をFlexboxで表示するように指定します。また、子要素の.block-grid liflex: 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を使った方法について、詳しくはこの記事(英語) あたりが参考になりそうです(手抜きですみません。。。)。

“表示件数と高さが変わるブロックの一覧を、画面サイズに合わせてカラム数を変えて表示する方法” への2件のフィードバック

  1. […] 表示件数と高さが変わるブロックの一覧を、画面サイズに合わせてカラム数を変えて表示する方法 […]

  2. 通りすがり より:

    inline-blockを指定したときは、親要素ulにfont-size:0を指定すると崩れなくなりますよー。
    liにはフォントサイズを指定します

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です