どうも、ダイチです。
今回はカード型レイアウトの作り方についてまとめました。
汎用的なWebデザインの一つですが、メディアクエリでカラムを変える時や、WordPresなどを想定して更新性を確保することを考えると意外と難しいです。
色々書き方を調べましたが、この記事で紹介するのはnth系の擬似クラスを使わずに簡単に構成できる方法となっています。
現時点での僕的ベストプラクティスなので、同じように悩んでる方は是非参考にしてみてください!
カード型レイアウトとは
カード型レイアウトとは、下のようにカードやタイル型のコンポーネントを並べるデザインのことです。
ブログサイトの記事一覧や、コーポレートサイトの実績紹介などで使われることも多く、同列の要素を一覧表示にしたい場合に取り入れられたりします。

複数行になる場合、一般的にはコンテナ要素の枠の中で、左から詰めるように配置されます。
画面幅の広いPC向けのレイアウトだったりするので、端末の幅に合わせてカラム(表示数)を切り替えたりと工夫が必要です。
カード型レイアウトを構成する時の注意点
表示されるアイテム数が一定であればいいのですが、ブログサイトのように並べられる要素数が変動するサイトの場合は注意が必要です。
<ul class="bl_flexContainer">
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
</ul>
コンテンツを枠の中で横に並べていくためのレイアウトを構成するだけであれば、みんな大好きFlexBoxで実現できます。
.bl_flexContainer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 10px;
}
.el_flexItem {
width: 30%;
height: 300px;
background-color: royalblue;
}

flex-wrap: wrapを指定しておけば、要素が増えた時にコンテナ内で自動で次の行に折り返してくれます。
また、カード型レイアウトでは左右のアイテムはコンテナ内の枠の端にぴったりと配置させることが多いので(もちろんpaddingも考慮したうえで)、justify-content: space-betweenを指定しています。内側のアイテムも等間隔で配置されて楽チンです。
しかしアイテムが下のように増えると、
<ul class="bl_flexContainer">
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
</ul>

このようになってしまいます。
表示要素の数が変動するブロックの場合は、左右要素を端に寄せたい+要素間のmarginを指定せずに等間隔で配置できる、といった理由で安易にjustify-content: space-betweenを使うのは危険です。
nth系の擬似クラスを使った構成方法
justify-content: space-betweenを使わずに要素間の余白を作るには、一つ一つにmarginを指定する必要があります。
当記事で紹介するのとは違うやり方ですが、比較のためにもnth系の擬似クラスを指定する方法を紹介します(僕も以前まではこのやり方でした)
<ul class="bl_flexContainer">
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
<li class="el_flexItem"></li>
</ul>
.bl_flexContainer {
display: flex;
flex-wrap: wrap;
padding: 20px;
}
.el_flexItem {
width: calc(100% / 3 - 40px / 3);
height: 300px;
margin-right: 20px;
margin-bottom: 30px;
background-color: royalblue;
}
3枚のカードを並べることを前提に指定しています。
各アイテムにmargin-right: 20pxとしていますが、このままだと当然右端にも余白ができてしまいます。

.bl_flexContainer {
display: flex;
flex-wrap: wrap;
padding: 20px;
}
.el_flexItem {
width: calc(100% / 3 - 40px / 3);
height: 300px;
margin-right: 20px;
margin-bottom: 30px;
background-color: royalblue;
}
.el_flexItem:nth-of-type(3n) {
margin-right: 0;
}
これを解消するために、nth-of-type(3n)とすることで3の倍数の時だけmargin-rightを0として打ち消す記述を追加します。
width: calc(100% / 3 - 40px / 3);
ちなみに、アイテムの幅はcalcを使用して計算しています。100%/3で33.333pxとして、ここから余白幅を引いています。
margin-rightでアイテム右に20px設けたので、カード3枚だったら余白合計は40px。これをアイテム数の3で割った数値がマイナスする余白幅です。

widthを100%/3としているので、画面幅が変わっても3カラムのレイアウトが崩れることはありません。
メディアクエリでカラム数を変えたい場合は、el_flexItemのwidthで割る数をカラム数(下の例だと2カラム)を変更します。また、nth-of-typeの数値もカラム数にあわせて指定し、margin-rightを0とします。
@media screen and (max-width: 599px) {
.el_flexItem {
width: calc(100% / 2 - 20px / 2);
}
.el_flexItem:nth-of-type(2n) {
margin-right: 0;
}
}

ただ、それだけだと上で指定した3nの指定が残ってしまっているので、打ち消しの指定も必要です。
@media screen and (max-width: 599px) {
.el_flexItem {
width: calc(100% / 2 - 20px / 2);
}
/* スタイルの打ち消し */
.el_flexItem:nth-of-type(3n) {
margin-right: 20px;
}
.el_flexItem:nth-of-type(2n) {
margin-right: 0;
}
}
注意点としては、詳細度の問題があるためスタイルの打ち消し(上だと3n)より新しい指定(上だと2n)は下に記述にしておく必要があります。CSSは同じ詳細度であれば下に記述されている指定を優先します。

もしくは親要素を被せたり別スタイルを重ねるなどで詳細度を上げる必要があります。
このように、カラム数とnthの数を合わせるやり方は、考え方として分かりやすいですが、Sassでネストが深くなって詳細度が変わったり、メディアクエリが増えてカラムの調整が入る場合など、どうしても記述が面倒になります。
なので最近はこのnth系の指定方法をやめて、今回紹介する構成方法に切り替えることにしました。
HTML/CSSコード全体紹介
前置きが長くなりましたが、本題の解説を進めます。
今回紹介するやり方を簡単にお伝えすると、アイテム要素に指定した分のmarginを、コンテナ要素からネガティブマージンで相殺してすっぽり収める、みたいな感じです。
先に全体のコードを記載しておきます。このままコピペして使うこともできます。
<div class="bl_media_container">
<div class="bl_media_itemWrapper">
<div class="bl_media_item">
<p class="img"><img src="img/..." alt=""></p>
<h3>ダミータイトル</h3>
<p>ダミーダミーダミーダミーダミーダミー</p>
</div>
</div>
<div class="bl_media_itemWrapper">
<div class="bl_media_item">
<p class="img"><img src="img/..." alt=""></p>
<h3>ダミータイトル</h3>
<p>ダミーダミーダミーダミーダミーダミー</p>
</div>
</div>
<div class="bl_media_itemWrapper">
<div class="bl_media_item">
<p class="img"><img src="img/..." alt=""></p>
<h3>ダミータイトル</h3>
<p>ダミーダミーダミーダミーダミーダミー</p>
</div>
</div>
<div class="bl_media_itemWrapper">
<div class="bl_media_item">
<p class="img"><img src="img/..." alt=""></p>
<h3>ダミータイトル</h3>
<p>ダミーダミーダミーダミーダミーダミー</p>
</div>
</div>
<div class="bl_media_itemWrapper">
<div class="bl_media_item">
<p class="img"><img src="img/..." alt=""></p>
<h3>ダミータイトル</h3>
<p>ダミーダミーダミーダミーダミーダミー</p>
</div>
</div>
</div>
/* BASE CSS */
* {
list-style: none;
box-sizing: border-box;
margin: 0;
padding: 0;
}
img {
width: 100%;
vertical-align: bottom;
}
/* ここからカードレイアウトのスタイリング */
/* PC 3カラム */
.bl_media_container {
display: flex;
flex-wrap: wrap;
margin: calc(-30px / 2);
padding: 30px;
}
.bl_media_itemWrapper {
width: calc(100% / 3 - 30px);
margin: calc(30px / 2);
}
.bl_media_item {
outline: 1px solid #000;
font-size: 1.5vw;
}
/* タブレット 2カラム */
@media screen and (max-width: 1024px) {
.bl_media_itemWrapper {
width: calc(100% / 2 - 30px);
}
}
/* スマホ 1カラム*/
@media screen and (max-width: 599px) {
.bl_media_itemWrapper {
width: calc(100% / 1 - 30px);
}
}
今回CSSの命名規則はPRECSSを参考にしています(我流入ってますが)。内容はこちらの名著で勉強できるので、コーディング勉強中の方は一度購入して読んでおくことをお勧めします。
カード型レイアウトのCSS解説
<div class="bl_media_container">
<div class="bl_media_itemWrapper">
<div class="bl_media_item"></div>
</div>
<!-- ...以下必要なアイテム数分記述 -->
</div>
- media_container・・・全体の枠
- media_itemWrapper・・・カードの大きさやmarginを指定する枠
- media_item・・・カード自体のスタイルを当てるクラス
ここで重要なのはmedia_containerとmedia_itemWrapperの扱い方です。
今回カラムは3カラムで、アイテム間の余白は上下左右30pxの設定にします。
.bl_media_container {
display: flex;
flex-wrap: wrap;
padding: 30px;
}
media_containerは全体の枠となるので、ここではflex-wrapを指定しておきます。あと構成には関係ないですが見やすいように一応paddingも指定してます。
.bl_media_itemWrapper {
width: calc(100% / 3 - 30px);
margin: calc(30px / 2);
}
media_itemWrapperは各アイテムに対するレイアウトを指定するクラスです。
まずmarinから解説します。
margin: calc(30px / 2)とすると上下左右に15pxの余白が作れます。隣のアイテムの余白と合計することで30pxになります。

わざわざ30px / 2にしたのは「隣り合わせた合計が設けたい余白である」ということを分かりやすくするためだけなので、直接15pxと書いてしまっても問題はありません。
次にwidthです。
こちらは100%をカラム数3で割った数値からmarginとなる余白を引いています。
ただnthを使った方法と違い、今回は一方向ではなく上下左右にmarginをあてているので、余白は下の図のような感じになっています。

左右のmargin合計が(15+30+30+15)で90pxになるので、これをカラム数3で割った「30px」を引いているということです。
ただ枠内に沿って配置されている要素含め、全ての要素の上下左右にmarginがあたっているので、図だと分かりづらいですがコンテナ内側に余白が生まれてしまっています。

これを消すためにコンテナ要素にネガティブマージンを指定します。
.bl_media_container {
display: flex;
flex-wrap: wrap;
margin: calc(-30px / 2); /* ←追記 */
padding: 30px;
}
アイテムに指定したmarginと同じ値にマイナス指定します。これで上下左右15px分内側の余白を消すことができます。
デベロッパールールだとネガティブマージンが色で表示されないので分かりづらいですが、コンテナ要素のpadding30pxに沿った位置にぴったりついてます。

これでカードレイアウトの完成です!
この指定方法の良いところはメディアクエリなどでカラムを切り替える場合に発揮します。余白は変えずに2カラムにしたい場合は、
@media screen and (max-width: 599px) {
.bl_media_itemWrapper {
width: calc(100% / 2 - 30px);
}
}
これだけでOKです。nthで指定する方法と比べるとめちゃくちゃ簡単で、アイテム幅を100% / 3 から 100% / 2 にするだけです。
SCSSのコード
ここからはおまけですが、SCSSの場合のコードも用意しました。
考え方は上と同じですが、変動するカラム数や余白を変数にまとめています。(mixinでまとめるともっと簡略化できます)
* {
list-style: none;
box-sizing: border-box;
margin: 0;
padding: 0;
}
img {
width: 100%;
vertical-align: bottom;
}
/***************************/
/* 変数 */
$interval: 60px; /* カード間の余白 */
$number: 3; /* カード表示枚数 */
/***************************/
.bl_media_container {
display: flex;
flex-wrap: wrap;
margin: $interval / -2;
padding: 20px;
}
.bl_media_itemWrapper {
width: calc(100% / #{$number} - #{$interval});
/* [100% / 表示させたいカード枚数] - [上下左右の枠にかかるマージン] */
margin: $interval / 2;
/* カード間の余白(隣接要素との合計になるため1/2を指定) */
}
.bl_media_item {
outline: 1px solid #000;
font-size: 1.5vw;
}
// 599px以下
@media screen and (max-width: 599px) {
/***************************/
/* 変数 */
$interval: 30px; /* カード間の余白 */
$number: 2; /* カード表示枚数 */
/***************************/
.bl_media_container {
margin: $interval / -2;
}
.bl_media_itemWrapper {
width: calc(100% / #{$number} - #{$interval});
margin: $interval / 2;
}
}
// 480px以下
@media screen and (max-width: 480px) {
/***************************/
/* 変数 */
$interval: 40px; /* カード間の余白 */
$number: 1; /* カード表示枚数 */
/***************************/
.bl_media_container {
margin: $interval / -2;
}
.bl_media_itemWrapper {
width: calc(100% / #{$number} - #{$interval});
margin: $interval / 2;
}
}
これで管理することで、コーディング後にデザイナーさんから「やっぱ余白をちょっと広げたい」「スマホの時も2カラムで」なんて変更が入った場合も即時で修正可能です!

CSSを変数に格納した場合のコード
IE非対応なのでまだ使うことは少ないかもしれないですが、SCSS同様、変数管理することで管理が強烈に楽になります。
* {
list-style: none;
box-sizing: border-box;
margin: 0;
padding: 0;
}
img {
width: 100%;
vertical-align: bottom;
}
/***************************/
/* 変数 */
:root {
--interval: 30px; /* カード間の余白 */
--number: 3; /* カード表示枚数 */
}
/***************************/
.bl_media {
background: #eff3fc;
}
.bl_media_container {
display: flex;
flex-wrap: wrap;
margin: calc((var(--interval) / -2));
padding: 30px;
}
.bl_media_itemWrapper {
width: calc((100% / var(--number)) - var(--interval));
/* [100% / 表示させたいカード枚数] - [上下左右の枠にかかるマージン] */
margin: calc((var(--interval) / 2));
/* カード間の余白(隣接要素との合計になるため1/2を指定) */
}
.bl_media_item {
outline: 1px solid #000;
font-size: 1.5vw;
}
@media screen and (max-width: 599px) {
/***************************/
/* 変数 */
:root {
--interval: 30px; /* カード間の余白 */
--number: 2; /* カード表示枚数 */
}
/***************************/
}
@media screen and (max-width: 480px) {
/***************************/
/* 変数 */
:root {
--interval: 40px; /* カード間の余白 */
--number: 1; /* カード表示枚数 */
}
/***************************/
}
まとめ
いかがでしたでしょうか?
記述が楽で使い回しもしやすく、ここにSCSSの変数管理を加えると保守性も高まります。
ただ本当はこういった縦横方向のレイアウトでは、display:gridが真骨頂を発揮するので最、ここまで紹介しといてアレですが、IEのことを考えなくていいならdisplay:gridを使いましょう(笑)
僕はまだ使わないですが、、、今回の書き方であればそこまで難易度も高く無いのでしばらくはこの書き方でいきたいと思います。
また、当記事を読まれている方の中にはWeb制作初学者の方もいるかと思いますが、基礎から体系的におさらいするには書籍での学習が適しています。こちらはとてもわかりやすく解説されている人気の書籍なので紹介しておきます!お勧めです!
それでは今回はこのへんで!
コメント