カードアイテムはブログやオンラインショップ、実績紹介、ニュースサイトなど様々な場面で見かける。コーディングする機会も多いと思う。カードアイテムのコーディングについて書いていこうと思う。
というのも、自分もカードアイテムのコーディングには「コンテンツの高さが揃わない!」「ボタンや一番下のテキストが下揃えにならない!」など散々苦しめられてきたので、改めてコーディングの整理と誰かの役に立てればいいなということで書いてみた次第。
間違っていることもあるかもしれないので、そういうのはどんどん指摘して欲しい。問い合わせからでもDMでもなんでもOK。
とりあえず結論(カード単体)
「説明はいいから結論書けよ」「早く物だけくれよ」というせっかちさんの為に完成形を書く。
以下が完成形のコード。
でもここまでに至るコーディングの流れも書くから、それも見て行ってくれるととても嬉しい。
<div class="card">
<article class="card__item">
<div class="card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="card__inner">
<p class="card__title">タイトル</p>
<p class="card__body">説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります</p>
<div class="card__footer">
<time datetime="2022-1-28">2022.01.28</time>
<p class="card__category">カテゴリー</p>
</div>
</div>
</article>
<article class="card__item">
<div class="card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="card__inner">
<p class="card__title">タイトル</p>
<p class="card__body">説明文が入ります</p>
<div class="card__footer">
<time datetime="2022-1-28">2022.01.28</time>
<p class="card__category">カテゴリー</p>
</div>
</div>
</article>
<article class="card__item">
<div class="card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="card__inner">
<p class="card__title">タイトル</p>
<p class="card__body">説明文が入ります</p>
<div class="card__footer">
<time datetime="2022-1-28">2022.01.28</time>
<p class="card__category">カテゴリー</p>
</div>
</div>
</article>
</div>
.card {
max-width: 800px;
margin: 0 auto;
display: flex;
}
.card__inner {
background-color: lightsteelblue;
padding: 10px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.card__item {
width: calc( (100% - 20px ) / 3);
border: 1px solid #ccc;
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
.card__item + .card__item {
margin-left: 20px;
}
.card__img {
position: relative;
width: 100%;
}
.card__img {
padding-top: calc( 10 / 15 * 100% );
position: relative;
}
.card__img img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.card__footer {
display: flex;
justify-content: space-between;
margin-top: auto;
}
まずは基本の形から作る
よくあるカードアイテムは以下のような感じ。
シンプルに画像・タイトル・テキストで構成されているもの。
これはそんなに難しくない。上から順番に作っていけばできる。
まずhtmlとCSSは以下のようになる。
<div class="normal-card">
<article class="normal-card__item">
<div class="normal-card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<p class="normal-card__title">タイトル</p>
<p class="normal-card__body">余白は一切ありません。説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります</p>
<p class="normal-card__category">カテゴリー</p>
</article>
</div>
.normal-card__item {
background-color: lightpink;
max-width: 251px;
width: 100%;
border: 1px solid #ccc;
}
まずは基本はこんな感じで組んでいく。
2つ以上のコンテンツ内容が異なるカードを並べたとき
カードアイテムは基本的に単体ではなく、大体1列に3個とか4個とか複数のカードアイテムを並べられるのが一般的。(スマホは1つのみの場合が多い)
ここでは先ほどのカードアイテムが1列に2個並べられた場合以下のように表示させる場合を考えてみる。
比較する為にコンテンツの中身を多いものと少ないものに分けて作成する。
実際にさっきのCSSのままで同じように実装すると、下のようになる。
カード毎に説明文の部分が異なるため、右のアイテムは左よりもコンテンツ部分が少なく上に詰まった形になってしまう。
この場合は以下のようにCSSを設定する。
.normal-card__item {
background-color: lightpink;
max-width: 251px;
width: 100%;
border: 1px solid #ccc;
display: flex; // 追加したCSS
flex-wrap: wrap; // 追加したCSS
flex-direction: column; // 追加したCSS
}
.normal-card__item + .normal-card__item {
margin-left: 20px;
}
.normal-card__category {
margin-top: auto; // 追加したCSS
}
.normal-card__itemにdisplay: flex;を追加する。
これによって、それぞれのカードアイテムの.normal-card__itemの高さが揃う。
flex-direction: column;は横並びを縦積みに戻すために追加する。
そして.normal-card__categoryにmargin-top: auto;を追加することで、マージンが上に設定されてカテゴリーが一番下に配置される。
カードの中身にpaddingを設ける
今のままだと端にべったりくっついた状態であるが、それだと詰まった感じになって非常に見にくくなるのでカードの中身には大体余白を設けることがほとんど。完成イメージは下みたいな感じ。余白があることで中身が見やすくなる。
<div class="normal-card__inner"></div>を追加し、その中に<p class="normal-card__title">以下のpタグを中に入れてあげる。
<div class="normal-card">
<article class="normal-card__item">
<div class="normal-card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="normal-card__inner">
<p class="normal-card__title">タイトル</p>
<p class="normal-card__body">余白は一切ありません。説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります</p>
<p class="normal-card__category">カテゴリー</p>
</div>
</article>
<article class="normal-card__item">
<div class="normal-card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="normal-card__inner">
<p class="normal-card__title">タイトル</p>
<p class="normal-card__body">normal-card__itemの中に要素を入れただけの場合</p>
<p class="normal-card__category">カテゴリー</p>
</div>
</article>
<article class="normal-card__item">
<div class="normal-card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="normal-card__inner">
<p class="normal-card__title">タイトル</p>
<p class="normal-card__body">normal-card__itemの中にflexした要素が入るとこうなります</p>
<div class="normal-card__footer">
<time datetime="2022-1-28">2022.01.28</time>
<p class="normal-card__category">カテゴリー</p>
</div>
</div>
</article>
</div>
.normal-card__item {
background-color: lightpink;
max-width: 251px;
width: 100%;
border: 1px solid #ccc;
display: flex; // 追加したCSS
flex-wrap: wrap; // 追加したCSS
flex-direction: column; // 追加したCSS
}
.normal-card__item + .normal-card__item {
margin-left: 20px;
}
.normal-card__inner {
background-color: lightcoral;
padding: 10px;
}
.normal-card__footer {
display: flex;
justify-content: space-between;
margin-top: auto;
}
.normal-card__category {
margin-top: auto; // 追加したCSS
}
※ここでは分かりやすいように.normal-card__innerに背景色つけてます。
そうすると、.normal-card__innerの中身が少ないとその下に空きスペースが発生する。またカテゴリーは上から順にテキストが配置される形になるので、最下部に配置されなくなる
斜線が空きスペースを表している。
.normal-card__innerにflex-grow:1;を追加する。
flex-growプロパティはflex-grow: n;でn分割した前述の空きスペースを割り当てることができる。
flex-grow:2;なら空きスペースを2分割、flex-grow:3;を3分割するといったイメージ。
ここでいえば、空きスペースを1分割して.normal-card__innerに足す。これを追加することで.normal-card__innerの高さが一番下まで伸びたことがわかる。
参考:https://developer.mozilla.org/ja/docs/Web/CSS/flex-grow
カテゴリー部分が下揃えになっていないので、あとは.normal-card__innerにもdisplay:flex;とflex-direction: column;を追加してカテゴリーを下揃えにする。
結論だけ書けばいいところをなぜ時系列のように書いたかというと、どのようなCSSを当ててその時にどのような状態になるかを把握した方が今後同じようなことが起こった時に対処できると思ったので書いてみた。
カードアイテムの並べ方
カード単体のコーディングが終わったので、今度は複数ある場合の並べ方をやってみる。
今回は1列に3つ並べて、2列作る。
.cardはwidth: 790px;とする
2つ方法があるのでNGを交えながらやっていく
カードアイテムの片側にマージンをつける
よくあるのがカードアイテムの片側にマージンつける方法。
.card {
max-width: 790px;
margin: 0 auto;
display: flex; // 追加
flex-wrap: wrap; // 追加
}
.card__item {
border: 1px solid #ccc;
display: flex;
flex-wrap: wrap;
flex-direction: column;
width: calc( ( 100% - 20px * 2) / 3 ); // 追加
}
.card__item:nth-child(3n+2),
.card__item:nth-child(3n+3) {
margin-left: 20px; // 追加
}
.card__item:nth-child(n+4) {
margin-top: 20px; // 追加
}
2番目、3番目、5番目、6番目…の左側にマージンをつける指定と、2列目以降(アイテム4つ目以降)にマージントップをつける指定をする。
この時カードアイテムのwidthは固定値で書くとウィンドウを狭くしたときに表示崩れを起こすので絶対固定値は書かないこと!
考え方としては(100% - マージンの幅 * マージンの個数) / 1列に並べるアイテム数をcalc()の中に書いてあげる。
ここでいうと、マージンの幅=20px、マージンの個数=2個、1列に並べるアイテム数=3個なので
(100% - 20px * 2) / 3となる。そうするとウィンドウ幅が狭くなったとしても表示崩れを起こすことは無くなる。
ちなみにマージンの左右、上下どちらを指定するかだが、僕は例外を除いて基本的にleftとtopにつける。
どちらにつけるかは正直好みでいいと思う。毎回違うとわけわからなくなるので、どちらかに統一はしておいた方が良い。
ただこの方法は修正する場合、直す項目が多く保守性が下がる。
マージンを調整する場合、.card__itemのwidth、.card__item:nth-child(3n+2), .card__item:nth-child(3n+3) のマージンの値の変更が発生する。.card__item:nth-child(n+4)の値の変更も発生する可能性がある。
NG例:.cardにjustify-content: space-between;をつける
.cardにjustify-content: space-between;をつけないこと!
カードが6つの時は良いが、5つなどの場合は2列目のカードが左右に配置され真ん中が穴が開いたように見えたり不自然な形になってしまうので使用しない。最初の頃はやりがち。
jQueryで空のカードアイテム要素を挿入して見た目を整える方法もあるが正直面倒だし、労力の無駄。
そんなまわりくどいことをするくらいなら最初からwidthで調整した方が早い。
ネガティブマージンでカードアイテムを並べる
カードアイテムにマージンをつけるのではなくパディングを当てて、カードアイテムを囲っているクラスにネガティブマージンで調整するやり方を書く。
最初はこの考え方が難しいかもしれないが、順を追って説明していく。
まず最初に先ほどまでカードアイテムにつけていたマージンを消す。.cardのmax-widthも消す。
するとこんな感じで右側に隙間ができる。これは.card__itemにwidth: calc( ( 100% - 20px * 2) / 3 );が指定されてるがマージンが無くなった為、無くなったマージン分の隙間が発生する為だ。
なので、.card__itemにwidth: calc( 100% / 3 );と単純に3分割するようにする。すると以下のように隙間が無くなる。
次に.card__itemにマージンではなくパディングを当ててあげる。
.card__item {
(中略)
padding-left: 20px;
padding-top: 20px;
}
すると全てのカードアイテムにパディングを当てている為、以下のように.cardに余白がついたような状態になってしまう。
そこで.cardの余白がついたような状態を解消するためにネガティブマージンを指定して打ち消しを行う。
.card {
(中略)
margin-left: -20px;
margin-top: -20px;
}
.cardでネガティブマージンを使用しているため、カードコンテンツの幅を指定・真ん中に寄せる際は.cardをさらにdivで囲ってCSSで整える(.card-wrapperとする)。
.card-wrapperにmax-width: 790px; width: 100%; margin: 0 auto;を指定する。
そうして最終的に出来たのが以下のコードだ。
<div class="card-wrapper">
<div class="card">
<article class="card__item">
<div class="card__img">
<img src="./assets/images/common/dummy.jpg" alt="ダミー">
</div>
<div class="card__inner">
<p class="card__title">タイトル</p>
<p class="card__body">説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります説明文が入ります</p>
<div class="card__footer">
<time datetime="2022-1-28">2022.01.28</time>
<p class="card__category">カテゴリー</p>
</div>
</div>
</article>
(中略)
</div>
</div>
.card-wrapper {
max-width: 790px;
width: 100%;
margin: 0 auto;
}
.card {
margin: 0 auto;
display: flex;
flex-wrap: wrap;
margin-left: -20px;
margin-top: -20px;
}
.card__inner {
background-color: lightsteelblue;
padding: rem(10);
flex: 1;
display: flex;
flex-direction: column;
}
.card__item {
display: flex;
flex-wrap: wrap;
flex-direction: column;
width: calc( 100% / 3 );
padding-left: 20px;
padding-top: 20px;
}
.card__img {
width: 100%;
padding-top: calc( 10 / 15 * 100% );
position: relative;
}
.card__img img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.card__footer {
display: flex;
justify-content: space-between;
margin-top: auto;
}
この書き方だとカードとカードの隙間は.card__itemのpadding-leftとpadding-topの値を変えるだけで終わる。
1列に並べるカードアイテムの数が変わった時は.card__itemのwidth: calc( 100% / 3 );の値の3の部分をカードの数に応じて変えるだけで済み、保守性は上がると思う。
めちゃくちゃ頑張って書いてみた。