flex-boxを使わないでおしゃれなレスポンシブカラムリストを作る

flex-boxってよくわからん

仕事でHTMLのコーディングをすると、以下のようなデザインを実装しなければならないことが多くなるのではないでしょうか。

(ZOZOTOWNのメンズシューズカテゴリ。さすがきれいですね。)
(※2018/1/5現在のキャプチャです)

これを作れと指示されたときに、真っ先に「flex-box」を頭に浮かべる人が多いと思いますが、実際にflex-boxを実際にやってみると、なんだか思ったとおりにいかないのも事実…
原因としては…

  • 親要素にdisplay: flexをかけると、子要素が問答無用で「flex-item」になる
  • flex-boxに関するプロパティが多すぎて覚えられない
  • 昔に仕様が乱立しすぎて、どの記事を信じたらいいのかがわからない(最近落ち着いて来ましたが…)
  • いくらautoprefixerを使っていても、IEやandroidで使えないプロパティが多い

などなど色々あると思います。
なんやかんやで壁にぶち当たって、奈落の底に突き落とされる人は多いはずです。

ならばと、inline-blockやtable-cellを使ってみる

ググっていて、flex-boxを使わなくてもinline-blockならできそう!!とやってみても、意図しない余白とかが出て思い通りのデザインができないこともしばしば。
HTMLの<!-- -->を使って余白を削除して…みたいな記事もあるが、「いやいやインデント取りたいんですけど!!」と怒り狂ってPCを放り投げてしまった人もいるでしょう。。。

display: table-cellを使ってみても、ul>liを行ごとに区切るようにマークアップを変えなきゃいけなくて、デザインとマークアップが依存していて嫌だなとか、phpなどで吐き出すときにめんどくさいな、などと感じて、PCを放り投げてしまった経験を持った人もいるでしょう。。。

そこで、救世主「float」

みなさんはfloatって知っていますか??
上記であげた、inline-blocktable-cellflex-boxの登場で、あまり光を浴びなくなってしまったのがこのfloat。。。
いろんな人から、「いやいやfloatなんて時代遅れだし扱い方わからないしこんなの使わないでしょ普通www」と思われているのも知っています。

でも、実はこのちょっと古くさいイメージのあるfloatを使って、おしゃれなレスポンシブカラムリストを作ることができるんです。

騙されたと思って実際にやってみてください。
ひとまずこんな感じでHTMLを書いてました。

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>itemList</title>
  <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <link rel="stylesheet" href="./reset.css">
  <link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="itemList">
  <ul class="itemList__items">
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品1</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品2</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品3</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品4</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品5</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
    <li class="itemList__item">
      <a href="#" class="itemList__link">
        <p class="itemList__photo"><img src="https://dummyimage.com/125x150/cccccc/" alt=""></p>
        <p class="itemList__name">商品6</p>
        <p class="itemList__price">¥1,234</p>
      </a>
    </li>
  </ul>
</div>
</body>
</html>

ふっつーなHTMLです。
スマホサイトなので、viewportのmetaタグを入れています。コピペでOKなやつです。
これにスタイルを当てていきましょう。

※resetとなるcssはEric Meyerのリセットをつかっています。これも読み込んでいる前提で話を進めていきます。
※今回はscssで書いていきますが、中身はcssの説明なので、どんなメタ言語で書いても、そのままcssを書いてもなんでも大丈夫です。

style.scss
@charset "UTF-8";

.itemList {
  color: #333;
  font-size: 14px;

  .itemList__link {
    // aタグだとリンクの色がついてしまうので、親の色を継承する
    // inheritをうまく使うと、全体の色が変わるときに親の色だけ変えればいいので便利
    color: inherit;
    text-decoration: none;
  }

  .itemList__name {
    font-size: 10px;
  }
}

まずはZOZOっぽくなるように基本的なスタイルを入れます。

こんな感じになりました。いい感じです。
これを3カラムにしていきましょう。

基本は「シンプル、そして強引に!」

まずは、「3カラム」なので、明示的に「3カラム」とスタイルを書いていきましょう。

style.scss
@charset "UTF-8";

.itemList {
  color: #333;
  font-size: 14px;

  .itemList__items {
    // floatを使うのでclearfixの記述をいれます。
    &::after {
      content: "";
      display: block;
      clear: both;
    }
  }
  // ここで「全体を3分割(33.33%)するぜ!」と明示的に書く
  // 横並びにしたいので、floatを忘れずに
  .itemList__item {
    float: left;
    width: 33.33%;
  }

  .itemList__link {
    color: inherit;
    text-decoration: none;
  }

  .itemList__name {
    font-size: 10px;
  }
}

見た目はこんな感じになるはずです。

ここでのポイントは以下の3つ。

  • .itemList__itemを3カラムにしたいので、width: 33.33%と入れる
  • .itemList__itemを横並びにしたいのでfloatを入れる
  • floatを使うので.itemList__itemsにclearfixを入れる

clearfixってなんじゃらほいな方はこちらの記事に詳しく書かれているので読んでみてください。
私は.clearfixというクラスを作るのが嫌なので、clearfixが必要な箇所にclearfixの記述を書いています。

これで「シンプル」に3カラムにすることができました。
コードを見たときに「あ、33.33%≒ 100% / 3)だから3カラムなのね」と、視認性も上がりました。お得!
(今回はscssで書いているので、実際に100% / 3と書くのもアリでしたね。。。)

ここから、余白や背景色を入れて、さらにZOZOに近づけておしゃれにしていきましょう。

style.scss
@charset "UTF-8";

.itemList {
  // 全体の背景色がわかりづらいので上下の余白取ってみました
  padding: 10px 0;
  // ZOZOっぽくなるように新たに背景色を塗ります
  background-color: #f3f3f3;
  color: #333;
  font-size: 14px;

  .itemList__items {
    &::after {
      content: "";
      display: block;
      clear: both;
    }
  }

  .itemList__item {
    float: left;
    width: 33.33%;
  }

  .itemList__link {
    // marginを取れるようにaタグをblock化します
    display: block;
    // ZOZOっぽくなるように要素毎の余白を取ります
    margin: 1px 0 0 2px;
    // 要素一つ一つの背景は白いので、塗ります
    background-color: #fff;
    color: inherit;
    text-decoration: none;
  }

  // .itemList__itemの横幅より画像が小さい場合に、真ん中に来るようにする
  .itemList__photo {
    text-align: center;
  }

  // .itemList__itemの横幅より画像が大きい場合は縮めて、
  // 小さい場合は画像の原寸のままになるようにする魔法の記述
  .itemList__image {
    width: auto;
    max-width: 100%;
    height: auto;
  }

  .itemList__name {
    font-size: 10px;
  }
}

これを適応するの以下のようになります。

いい感じになってきました。
ここでのポイントは以下です。

  • .itemList__itemmarginを取りたくなるところを、.itemList__linkmarginを取る
  • 画像の伸縮と、解像度が荒れないようにするための指定を入れる

1つ目の、margin.itemList__itemに入れない理由は、.itemList__itemmarginを入れてしまうと、せっかく33.33%にしているのにmarginのせいで33.33% * 3 ≒ 100%の式が崩れてしまうからです。
(33.33% + margin-left) * 3という式になってしまって100%をオーバーしてしまうので、.itemList__linkで余白を取るのがベストなのです。

2つ目の画像の伸縮ですが、画面を横にした時のことを考えるとわかりやすいです。

画像が横幅より大きくなったときに、画像が横に伸びてしまうと解像度が荒れてきれいではなくなってしまいます。
この指定を入れると、横幅が伸びても画像の最大幅まで拡大させて、横幅が小さいときは横幅に合わせて縮んでくれるようになります。
もし、解像度が荒れてでも横幅と同じにしたい場合は、width: 100%height: autoを入れれば問題ありません。

あとは、余白をいい感じにしていけばいい、、、のですが、
実はまだ、デザイン上解決しなければいけない問題があります。

この画像は、左側を拡大したものです。
左側に、余白が空いてしまっていて後ろの背景が見えてしまっています。ZOZOは見えていないのに。。。
これは解決しないと、デザイナーに「なんで余白入っているんですか!!ありえないんですけど!!」と怒られてしまいます。人間関係の崩壊の始まりです。

これを解決するには、.itemList__itemの1番目、4番目、7番目…の子要素のmargin-left0にしていけばいいわけですよね。
なので、:nth-child(3n+1)を使って…となるわけですが、これは完全に「シンプル」ではなくなるのでポリシーに反します

なので、「強引」に解決しましょう。

style.scss
@charset "UTF-8";

.itemList {
  padding: 10px 0;
  background-color: #f3f3f3;
  color: #333;
  font-size: 14px;

  .itemList__items {
    // .itemList__linkに入れたmarginの値を負の値にしたものをここに入れる
    margin: -1px 0 0 -2px;

    &::after {
      content: "";
      display: block;
      clear: both;
    }
  }

  .itemList__item {
    float: left;
    width: 33.33%;
  }

  .itemList__link {
    display: block;
    margin: 1px 0 0 2px;
    background-color: #fff;
    color: inherit;
    text-decoration: none;
  }

  .itemList__photo {
    text-align: center;
  }

  .itemList__image {
    width: auto;
    max-width: 100%;
    height: auto;
  }

  .itemList__name {
    font-size: 10px;
  }
}

孫が上側と左側に余白をあけてしまったなら、祖父母が強引に元に戻してやればいいわけです。
孫がやってしまったことは祖父母が責任を負う時代です。

marginはマイナスの値を入れることで余白を詰めることができるので、それをうまく活用しています。これを「ネガティブマージン」といいます。そのままですね。

実は.itemListpadding: 10px 0としていたわけですが、.itemList__linkmargin-topによって、.itemListの上側の余白が11pxに見えるようになっていました。
これも.itemList__itemsのネガティブマージンで解決できています。
.itemList__linkmargin: 10px 0 0 10pxとかけてみると余白が広くなってデザインの気持ち悪さが顕著に表れてくるので、ぜひ試してみてください。
.itemList__linkmargin: 10px 0 0 10pxにしたら、.itemList__itemsmargin: -10px 0 0 -10pxとかければいいわけです。簡単!)

あとは、ZOZOっぽく、細かい余白を調整していってあげれば完成です。

style.scss
@charset "UTF-8";

.itemList {
  padding: 10px 0;
  background-color: #f3f3f3;
  color: #333;
  font-size: 14px;

  .itemList__items {
    margin: -1px 0 0 -2px;

    &::after {
      content: "";
      display: block;
      clear: both;
    }
  }

  .itemList__item {
    float: left;
    width: 33.33%;
  }

  .itemList__link {
    display: block;
    margin: 1px 0 0 2px;
    background-color: #fff;
    color: inherit;
    text-decoration: none;
  }

  .itemList__photo {
    text-align: center;
  }

  .itemList__image {
    width: auto;
    max-width: 100%;
    height: auto;
  }

  .itemList__name,
  .itemList__price {
    padding: 0 5px;
  }

  .itemList__name {
    margin-top: 5px;
    font-size: 10px;
    line-height: 15px;
  }

  .itemList__price {
    margin-top: 3px;
    line-height: 19px;
  }
}

これを適応するとこんな感じになります。

うん、ZOZOっぽい!
おしゃれになりましたね。
(余白は今回適当に入れています)
dummy画像ではなく、実際に画像を入れてみるともっといい感じになるので、lorempixelなどを活用して実際に入れてみるとテンションが上がると思います。

なんなら、画面を横にした時のことも考えたい

今回の作り方を使えば、「画面を横にした時(画面幅が多い時)には4カラムにする」というのも簡単にできます。

style.scss
@charset "UTF-8";

.itemList {
  padding: 10px 0;
  background-color: #f3f3f3;
  color: #333;
  font-size: 14px;

  .itemList__items {
    margin: -1px 0 0 -2px;

    &::after {
      content: "";
      display: block;
      clear: both;
    }
  }

  .itemList__item {
    float: left;
    width: 33.33%;

    // 画面幅が480px以上のときは、4カラム(25%)にする
    @media (min-width: 480px) {
      width: 25%;
    }
  }

  .itemList__link {
    display: block;
    margin: 1px 0 0 2px;
    padding-bottom: 15px;
    background-color: #fff;
    color: inherit;
    text-decoration: none;
  }

  .itemList__photo {
    text-align: center;
  }

  .itemList__image {
    width: auto;
    max-width: 100%;
    height: auto;
  }

  .itemList__name,
  .itemList__price {
    padding: 0 5px;
  }

  .itemList__name {
    margin-top: 5px;
    font-size: 10px;
    line-height: 15px;
  }

  .itemList__price {
    margin-top: 3px;
    line-height: 19px;
  }
}

@mediaを使って実装します。(mediaqueryというやつですね)
画面幅の決めの値(今回は480px)を入れて、その時にはwidth: 25%にするよという指定をしてあげればOKです。
widthを指定してあげれば変えられちゃうので、このやり方で実装しておけば、高度なレスポンシブWebも簡単に作ることができます。

まとめ

「シンプル、そして強引に」よくあるカラムリストを作ることができました。めでたし。
今回のポイントを以下にまとめてみました。

  • カラム数の横幅をパーセントで決める(3カラムなら33.33%、4カラムなら25%)
  • カラムの余白はカラムの幅を決めている子要素で取る
  • カラムの子要素で取った余分な余白はカラムの親にネガティブマージンをかけて解決する
  • 画像の幅はいい感じに可変するように、魔法の指定をかける

この要点を抑えてCSSを書いていけば、floatでも素敵なカラムリストを作ることができます。

追伸

今回、ZOZOのUIを取り上げてみましたが、ZOZOはお気に入りボタンや割引率表記などもあるので、今回のコーディングがZOZOの商品リストを実装する上でベストであると言いたいわけでは決してないので、そこだけはご了承ください。
(あとで見返したら商品のクリック領域とかも違くて全然再現できていないのでご容赦ください。。)

また、今回ご紹介したfloatによる実装にも欠点はあります。一つ一つの要素(今回で言う.itemList__item)の高さが異なってしまう場合は、今回のやりかただとガタガタに崩れてしまうので、結局nth-childclear: bothを駆使したり、そもそもinline-blockflex-boxを活用した方がいいこともあります。
必ずしもこのfloatでのやり方がいいわけではなく、その時折にあったやり方を取捨選択するのが良いと思います。
さらに、inline-blockで横並びさせる場合も、今回紹介したネガティブマージンの手法は活用できますので、いろいろなやり方を組み合わせて創造的にCSSを書いていくと、あらゆるデザインに対応できるはずです。
古い手法でも、うまくやればナウいデザインを作れるのだなーというのことを知ってもらえると嬉しいです。

[紹介元] HTMLタグが付けられた新着投稿 – Qiita flex-boxを使わないでおしゃれなレスポンシブカラムリストを作る

関連記事