【HTML&CSS】カード型レイアウト(モジュール)のコーディング方法

【HTML&CSS】カード型レイアウト(モジュール)のコーディング方法

この記事を書いた人

だいち

PENGIN BLOGメディア編集長。Web業界とは異業種の仕事をしながら、独学でWeb制作の世界に。副業でHP制作やコーディング代行、個人ブログの運営などに取り組み、現在はPENGINにてWebライティングやディレクションをしつつ、メディア運営全般を担当しています。(個人運営ブログ:https://daib-log.com/ )

今回はカード型(タイル型)レイアウトの作り方についてまとめました。

汎用的なWebデザインの一つですが、メディアクエリでカラム(縦方向の表示数)を変える時や、WordPresの記事一覧のようにカードの表示枚数が変動するサイトの場合を考慮したら意外と難しいデザインだったりします。

いくつか実装方法はありますが、この記事で紹介するのはnth系の擬似クラスやcss gridプロパティを使わない、初学者の方でも簡単に取り入れられる方法となっています。

一歩進んだテクニックを身につけたい方向け

同じように悩んでる方は是非参考にしてみてください!

>> コードだけ先に見たい方はこちらから(スクロールします)

カード型レイアウトとは

カード型レイアウトとは、下のようにカードやタイル型のコンポーネントを並べるデザインのことです。

ブログサイトの記事一覧や、コーポレートサイトの実績紹介などで使われることも多く、同列の要素を一覧表示にしたい場合に取り入れられたりします。

レイアウト例

複数行になる場合、一般的にはコンテナ要素の枠の中で、左から詰めるように配置されます。

PC向けのレイアウトだったりするので、画面幅に合わせてカラム(表示数)を切り替えたりと、実装には工夫が必要です。

カード型レイアウトを構成する時の注意点

表示されるアイテム数が一定であればいいのですが、ブログサイトのように並ぶ数が変動するサイトは注意が必要です。

<ul class="bl_flexContainer">
    <li class="el_flexItem"></li>
    <li class="el_flexItem"></li>
    <li class="el_flexItem"></li>
</ul>
.bl_flexContainer {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	padding: 10px;
}
.el_flexItem {
	width: 30%;
	height: 300px;
	background-color: royalblue;
}

アイテムを並べるだけであれば、みんな大好きFlexBoxで実現できます。

flexbox

flex-wrap: wrapを指定しておけば、要素が増えた時にコンテナ内で自動で次の行に折り返してくれます。

また、カード型レイアウトでは左右のアイテムはコンテナ内の枠の端にぴったりと配置させることが多いので(もちろんpaddingも考慮したうえで)、justify-content: space-betweenを指定。これならmarginを指定せずにアイテムを等間隔で配置できるので楽チンです。

しかしアイテムが下のように増えると、

<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>
Image from Gyazo

このようになってしまいます。

表示要素の数が変動するブロックの場合、左右要素を端に寄せたいからといった理由で、安易にjustify-content: space-betweenを使うのは危険です。

nth系の擬似クラスを使った構成方法

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枚のカードを並べることを前提に指定しています。

width: calc(100% / 3 - 40px / 3);

アイテム幅のところを説明しておくと、100% / 3で33.333pxとして、ここから余白幅を引いてます。

カード幅計算方法

margin-rightでアイテム右に20px設けたので、カード3枚だったら余白合計は40px。これをアイテム数(3)で割った数値がマイナスする余白幅です。

widthを100% / 3としているので、画面幅が変わっても3カラムのレイアウトが崩れることはありません。

margin-rightが入ってしまっている画像

ただこの状態だと、各アイテムに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で打ち消す記述を追加します。

@media screen and (max-width: 599px) {
	.el_flexItem {
		width: calc(100% / 2 - 20px / 2);
	}
	.el_flexItem:nth-of-type(2n) {
		margin-right: 0;
	}
}

メディアクエリでカラム数を変えたい場合は、カードのwidthで割る数(上の例だと2カラム)を変更します。

また、nth-of-typeの数値もカラム数にあわせて指定し、margin-rightを0とします。

Image from Gyazo

ただ、それだけだと上で指定した3nの指定が残ってしまっています。

2カラムにするブレイクポイント範囲では2n(2の倍数)のmargin-rightを0にして、3n(3の倍数)のmargin-rightは20pxないといけないため、打ち消しの記述が必要になります。

@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;
	}
}

上のようにカラム数に合わせて必要なmargin-rightを指定します。

注意点としては、詳細度の問題があるため、スタイルの打ち消し(上だと3n)より、新しい指定(上だと2n)は下に記述にしておく必要があります。

Image from Gyazo

もしくは親要素を被せたり別スタイルを重ねるなどで詳細度を上げる必要があります。

このように、カラム数とnthの数を合わせるやり方は考え方として分かりやすいですが、Sassでネストが深くなって詳細度が変わったり、メディアクエリが増えてカラムの調整が入る場合など、どうしても記述が面倒になります。

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クラスにスタイリングします。

アイテム間の余白

まずアイテム間の余白margin: calc(30px / 2)について。

アイテム自体に上下左右15pxの余白を作ることで、結果的に隣接するアイテムとの余白が30pxになるようにします。

つまり、margin:calc(30px / 2)は、アイテム自体の余白を15pxにするための指定、ということです。

Image from Gyazo

「隣り合わせた合計が設けたい余白(=30px)である」ということを分かりやすくするために30px / 2と書いてるだけなので、calcなど使わず直接15pxと書いてしまっても問題はありません。

アイテムの幅

次にアイテム幅のwidth: calc(100% / 3 – 30px)について。

アイテム3カラムなので幅100%を3で割りますが、そこから余白分も引かないといけません。

さきほど上下左右にmarginをあてたので、現在余白は下の図のようになっています。

上下左右にmarginがあたっている図

見て分かる通り、左右の余白は(15 + 30 + 30 + 15)で、合計90pxのmarginがあります。

このmargin合計90pxを3カラムのアイテムに等分させると、90px / 3 = 30px ですよね。

つまり、width: calc(100% / 3 – 30px)というのは、アイテム幅 100% / 3 から、カード1枚当たりに等分された余白30pxを引いた数値、ということになります。

※ここはnthでカード幅の計算をした時の図にて説明したのと同じような考え方です(クリックしたら上にスクロールします)

カードを囲む枠の内側余白について

枠内に沿って配置されている要素の上下左右にはmarginがあたっているので、図だと分かりづらいですが、コンテナ内側にも余白が生まれてしまっています。

ネガティブマージン設定前

これを消すためにコンテナ要素にネガティブマージンを追加します。

.bl_media_container {
	display: flex;
	flex-wrap: wrap;
    margin: calc(-30px / 2); /* ←追記 */
	padding: 30px;
}

アイテム間の余白に指定したmarginと同じ値でマイナス指定します。

これで上下左右15px分内側の余白を消すことができます。

デベロッパールールだとネガティブマージンが色で表示されないので分かりづらいですが、コンテナ要素のpadding30pxに沿った位置にぴったりついてます。

Image from Gyazo

これでカードレイアウトの完成です!

レスポンシブ対応時

この指定方法はメディアクエリなどでカラムが切り替えるデザインの場合に効果を発揮します。

たとえば余白は変えず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カラムで」なんて変更が入った場合も即時で修正可能です!

Image from Gyazo

CSSを変数に格納した場合のコード

SCSS同様、CSS変数で管理することも可能。

* {
	list-style: none;
	box-sizing: border-box;
	margin: 0;
	padding: 0;
}
img {
	width: 100%;
	vertical-align: bottom;
}
/***************************/
/* 変数 */
:root {
	--interval: 30px; /* カード間の余白 */
	--number: 3;      /* カード表示枚数 */
}

@media screen and (max-width: 599px) {
	:root {
		--interval: 30px; /* カード間の余白 */
		--number: 2;      /* カード表示枚数 */
	}
}

@media screen and (max-width: 480px) {
	:root {
		--interval: 40px; /* カード間の余白 */
		--number: 1;      /* カード表示枚数 */
	}
}

/***************************/
.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;
}

CSS変数についてはこちらの記事で解説しています!

まとめ

いかがでしょうか?記述が楽で使い回しもしやすく、SCSSの変数管理を加えると保守性も高められる方法でした。

少々ハック的なやり方ではありますが、理屈さえ理解できれば初学者の方でも取り入れやすいプロパティしか使っていません。

ただ、2022年のIEサーポト終了にともない、使用できるCSSの種類がグッと広がります。グリッド状のコーディングに最も適している「display: grid」が使えるようになることは大きいです。

こちらの記事もチェックしていただき、コーディングのバリエーションを増やしておきましょう!

また、当記事を読まれている方の中にはWeb制作初学者の方もいるかと思います。デザインやコーディングの基礎知識を学びたい方向けの記事を用意しているので是非見ていってください!

PENGIN無料コーディング課題

オススメ書籍紹介

オススメUdemy講座紹介

それでは今回はこのへんで!