カード型コンポーネントの僕なりのHTMLとCSSを紹介

こんにちは。
最近、今更ながらPodcastって面白いなぁーとワクワクしてます。

どうも、しばひろです。

ということで、Shibajukuでもポッドキャストを始めてみました。

Shiabajukuのサロンメンバーたちの会話を盗み聞きしてもらうというポッドキャスト「シバジュク酒場」です。

さて、今日は、色んなWebサイトでよく見かけるカード型コンポーネントについて書いてみたいと思います。

というのも、よく見かけるコンポーネントだからこそ、色んなマークアップ方法があると思うのですが、僕なりのマークアップ方法が誰かの参考になるかもしれないし、色んなコンポーネントのマークアップを考えるきっかけになるかもしれないので書いてみようと思いました。

カード型コンポーネントって何?

まず、カード型コンポーネントがどんなコンポーネントかっていうと、まるでカードのような縦型になっていて、一般的には、最初に画像があって、その下にタイトルや説明文があるような、よく見るコンポーネントです。

画像が先頭にあることで誘目性も高く、商品の一覧ページなどで、その商品の商品名や説明文よりも、商品の見た目が重要な商品の一覧表示に効果的で、先頭にある画像でパッとその商品に興味を惹く事ができるなどの理由でよく採用されるコンポーネントです。

ま、見た方が早いですね。こんなやつです。

See the Pen jOPqeYx by Shiba Hiro (@hilosiva) on CodePen.36915

ほら、よく見かけますよね。

ECサイトの商品一覧ページなどで見たことあるでしょ?

こんなよく見るコンポーネントひとつ挙げても、マークアップの方法は何パターンかあるんですよね。

なので、めちゃくちゃ恥ずかしいですが、僕なりのマークップ方法を紹介してみたいと思います。

僕なりのカード型コンポーネントのHTML

まず、結論からいうと、今回僕はこんなマークップにしてみました。

sample.html
<article class="card">
  <div class="card__header">
    <h3 class="card__title">正義Tシャツ</h3>
    <figure class="card__thumbnail">
      <img src="https://shibajuku.net/wp/wp-content/uploads/2020/02/seigiT.jpg" alt="手書きの「正義」という文字が縦に大きくマジックで書かれている白いTシャツ" class="card__image">
    </figure>
  </div>
  <div class="card__body">
    <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
    <p class="card__text -number">&yen; 15,000</p>
  </div>
  <div class="card__footer">
    <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
  </div>
</article>

ま、とりあえずややこしいから、<div> タグは無視しましょう。

sample.html
<article class="card">
  <h3 class="card__title">正義Tシャツ</h3>
  <figure class="card__thumbnail">
    <img src="https://shibajuku.net/wp/wp-content/uploads/2020/02/seigiT.jpg" alt="手書きの「正義」という文字が縦に大きくマジックで書かれている白いTシャツ" class="card__image">
  </figure>

  <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
  <p class="card__text -number">&yen; 15,000</p>

  <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
</article>

そうすると、こんな感じですかね。

「え、そこって <article> なの?」、とか「え、<figure> なの?」って議論は、ここでは一度置いておいて欲しいです。

今回のポイントは画像なんですよね。

ようは、<img> タグをどこに配置するのかって話なんです。

コンテンツとしての画像なのか、装飾としての画像なのか。

まず、このカード型コンポーネントは、何も考えずにマークアップすると、以下のようにマークアップをすることが多いんじゃないでしょうか?

sample.html
<article class="card">
  <figure class="card__thumbnail">
    <img src="https://shibajuku.net/wp/wp-content/uploads/2020/02/seigiT.jpg" alt="手書きの「正義」という文字が縦に大きくマジックで書かれている白いTシャツ" class="card__image">
  </figure>

  <h3 class="card__title">正義Tシャツ</h3>
  
  <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
  <p class="card__text -number">&yen; 15,000</p>

  <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
</article>

つまり、見たままの順番で行けば、

  1. 画像
  2. 見出し
  3. テキスト(説明文や金額、リンク)

なので、そのままHTMLをマークアップするのが多いんじゃないでしょうか?

もちろん、仕様書的には問題ありません。

ただ、例えば、「VoiceOver」などの音声読み上げ機能を使って、Webサイトを閲覧しているユーザーの中には、見出し<h1><h6>にジャンプする機能を使って、知りたい情報の見出しまでジャンプして、そこから情報をソフトに読み上げてもらっているユーザーもいます。

VoiceOver
macOSやiOSなどに入っているスクリーンリーダーで、MacやiPhoneの操作を音声説明で使用することができたり、Webサイトの内容を読み上げたりすることができる

つまり、今回のケースだと、「正義Tシャツ」という見出しにジャンプしてきたユーザーは、その上に記述されている モデルさんがおしゃれに正義Tシャツを着こなしている画像に気づことなく、読み進められちゃうってことです。

なので、ここでポイントになるのが画像の役割なんです。

ようは、この画像がユーザーに取って情報として必要な画像、つまりコンテンツとしての画像なのか、アイコンなどのように装飾としての画像なのかがポイントになると思っています。

もし、その画像がコンテンツとしての画像なのであれば、やはりユーザーにとっては必要な情報なので、HTML上は見出しの下に配置してスクリーンリーダーでも読み上げられる方が好ましいかと思います。

そうすることで、目で見てるユーザーと、音声で聞いてるユーザーとで情報の相違がなく伝達することができるからです。

反対にアイコンなどのように、装飾として使いたい画像なのであれば、そもそも装飾はCSSの仕事なので <img> タグじゃなく、CSSのbackground-image とかでもいいのかもしれません。

その場合は、以下のようになりますかね。

sample.html
<article class="card">
  <h3 class="card__title">正義Tシャツ</h3>
  
  <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
  <p class="card__text -number">&yen; 15,000</p>

  <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
</article>

シンプルですね。
もしくは、alt 属性を空にした <img alt=""> なら、先に画像を配置するのも、無しではないのかもなーと思ったりもします。

sample.html
<article class="card">
  <figure class="card__thumbnail">
    <img src="https://shibajuku.net/wp/wp-content/uploads/2020/02/seigiT.jpg" alt="" class="card__image">
  </figure>

  <h3 class="card__title">正義Tシャツ</h3>
  
  <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
  <p class="card__text -number">&yen; 15,000</p>

  <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
</article>

でもその場合は、ちゃんと <section> や、<article> などのセクショニング・コンテンツの要素でカードの範囲をマークアップしておいた方がいいと思います。

じゃないと、その画像がどの話題に対する画像かがわかりにくくなっちゃいます。

つまり、<section> などを使わずに、見出しで階層構造を作るとなると、機械的にHTMLを見た場合、どこまでが話題の終わりかが判断しにくく、次の見出しが出てくるまでが一つの話題として解釈できちゃいます。

そうなると、<h3> より上に画像があれば、その<h3>の直前までが、前の話題と解釈できるので、その画像は、手前の話題の見出しに対する画像かな?となっちゃう可能性もあるのです。

意図しないセクションの画像として解釈した例

そう解釈されないためにも <section> などのセクショニング・コンテンツの要素で話題の範囲をマークアップしておいた方がいいと思います。

今回のケースの画像はコンテンツ?それとも装飾?

で、今回のケースの場合、あの画像がコンテンツなのか、装飾なのかというと、これは僕としてはコンテンツという解釈です。

これは僕が昔アパレルでショップ定員していたから感じることなのかもしれませんが、やっぱりお客様って、商品の機能的な説明より、服の形とか自分に似合うのか?とか、すでによく似た物を持ってるじゃないか?、イケてるのか?、ナウいのか?、モテるか?って感じで、まずはその服の見た目の情報から入ると思うんですよね。そう考えれば、商品の写真や、モデルさんが着用してる写真は、それを伝えてる情報なので、コンテンツとして判断しました。

なので、最初に書いたようなマークアップにしてみました。

sample.html
<article class="card">
  <div class="card__header">
    <h3 class="card__title">正義Tシャツ</h3>
    <figure class="card__thumbnail">
      <img src="https://shibajuku.net/wp/wp-content/uploads/2020/02/seigiT.jpg" alt="手書きの「正義」という文字が縦に大きくマジックで書かれている白いTシャツ" class="card__image">
    </figure>
  </div>
  <div class="card__body">
    <p class="card__text">ごく普通の生地の白いTシャツに油性マジックで「正義」と書いただけの架空の半袖Tシャツです。</p>
    <p class="card__text -number">&yen; 15,000</p>
  </div>
  <div class="card__footer">
    <p class="card__text"><a href="#" class="button -compact">正義Tシャツの詳細を見る</a></p>
  </div>
</article>

でも、このままじゃ、デザインとは見た目の順番が違いますよね?

See the Pen RwPRmWb by Shiba Hiro (@hilosiva) on CodePen.36915

そこは、CSSの出番かなと。

CSSで画像と見出しを入れ替える

さて、画像と見出しを入れ替える方法はいくつかあります。

手軽なのは、flexbox の order プロパティで入れ替えるのが楽なんじゃないかなぁと思います。

他にもflexが使えなかった頃は position プロパティを使って入れ替えてました。

今回のサンプルでは、flexboxの order を使って入れ替えたパターンですね。

sample.css
.card {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, .16);
  color: #212121;
  text-decoration: none;
}

.card__header {
  display: flex;
  flex-wrap: wrap;
}

.card__title {
  padding: 1rem 1rem 0;
  font-size: 1.25rem;
  order: 1;
}

.card__thumbnail {
  margin: 0;
  order: 0;
}

.card__image {
  width: 100%;
}

.card__body {
  padding: 1rem;
}

.card__text {
  font-size: .75rem;
}

.card__text + .card__text {
  margin-top: .5rem;
}

.card__text.-number {
  text-align: right;
}

.card__footer {
  padding: 1rem;
  border-top: 1px solid #ddd;
}

これも色々とやり方があるので、今回の僕のコードだけが正解と思わないで下さいねw

少し解説すると、一応今回、個人的に拡張性をちょっと考えて、見出しと画像を <div> で囲み、その <div> に、display: flexを指定しました。

そして、その中の見出しにorder: 1を、画像の <figure>order: 0で入れ替えてみました。

See the Pen jOPqeYx by Shiba Hiro (@hilosiva) on CodePen.36915

ってな感じです。

ちなみに position でやるとこんな感じですかね。

sample.css
.card {
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, .16);
  color: #212121;
  text-decoration: none;
}

.card__header {
  position: relative;
  padding-top: 70.625%;
}

.card__title {
  padding: 1rem 1rem 0;
  font-size: 1.25rem;
}

.card__thumbnail {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
}

.card__image {
  width: 100%;
}

.card__body {
  padding: 1rem;
}

.card__text {
  font-size: .75rem;
}

.card__text + .card__text {
  margin-top: .5rem;
}

.card__text.-number {
  text-align: right;
}

.card__footer {
  padding: 1rem;
  border-top: 1px solid #ddd;
}

See the Pen Card component Use position by Shiba Hiro (@hilosiva) on CodePen.36915

<figure>position: absolute を付けて上に持ってきて、position: relative をつけた親要素に、画像の高さ分の padding-topを付けるパターンですね。

padding-top は、%で指定すると、レスポンシブで可変する時に便利です。

padding は%で指定したら、例え上下で親要素幅に対する割合なので、画像の高さを、カードの幅に対しての割合で指定する感じです。

ま、通常カードの幅と画像の幅が一緒のパターンが多いので、その場合は・・・

画像の高さ ÷ 画像の幅 × 100

で計算出来ます。

まとめ

どうでしょうか?

ま、色々議論はあるとは思いますが、僕はこんな感じで書いてます。

で、今回僕は、僕が書いたこのマークアップが正解ということを言いたいわけではなく、ちゃんと考えてマークアップすることって大事だよね?ってことを伝えたいだけです。

つまり、いくらデザインがそうだからと言って、そのまま見た目通りにマークアップするんじゃなくて、ちょっと立ち止まって、これってどの順番で書く方が伝わり方としてスムーズなのかなとか、どのタグがベストかなってのをちょっと意識して書くようになるだけで、アクセシビリティなのかなと思います。

ちょっとでもこの記事が、マークアップを考えるきっかけになれば嬉しいです。

基本的にShibajukuのマークアップに関する授業は、こんな話ばかりしてますw

では。