PCとスマホでレイアウトが違う場合のCSSをじっくりコーディング

PCとスマホで、見出し・文章・画像のレイアウトが異なるデザインデータが支給された場合のCSSコーディングについて解説します。もう「組めない」とは言わせない!

はじめに

レスポンシヴサイトのデザインデータで、PCとスマホでレイアウトが異なるというケースはよくあります。

つい先日コーディングしたサイトでは、以下のようなレイアウトが指定されていました。

PCとスマホでレイアウトが異なる例

大きく異なるのは見出しの位置です。PCでは画像の横に見出しと文章がありますが、スマホでは見出しの下に文章と画像が横並びで配置されています。

メディアクエリーでPCとスマホのスタイルを分けている場合、こういったレイアウトの違いは厄介です。floatやFlexboxを使うことを考えた場合、基本的にはHTMLの構造を変えなければなりません。

同じHTMLで組むのは難しい

以前の私だったら、このデザインデータを受け取っても「PCとスマホでレイアウトが変わっちゃっているので、どちらかに寄せていいですか?」などと答えて実装を諦めていました。しかし現在、この課題はCSSで解決できるようになっています。

この記事では、1つのHTMLから、CSSによってPCとスマホで異なるレイアウトを実現する方法を、じっくり解説します。

設計

CSSグリッドレイアウトを使う

今回は、CSSグリッドレイアウト(display: grid)を使う方針で考えます。

Can I Useによると、CSSグリッドレイアウトは記事執筆の2020年3月時点で主要ブラウザのほとんどをカバーしています。

CSS Grid Layout Module Level 1 - Can I Use

Can I UseによるとCSSグリッドレイアウトは主要ブラウザのほとんどをカバーしている

IEのみ、ベンダープレフィックスが必要となっています。IE対応まで必要な場合は、Autoprefixerを使うなどして対応してください(手書きで対応するのはオススメできませんが、一応記事後半にIE対応のサンプルコードを記載します)。

CSSグリッドレイアウトを使う場合、HTMLは以下のような形になります。

html
<div class="contents">
  <h2 class="contents__title">記事のタイトル</h2>
  <img class="contents__image" src="https://picsum.photos/400/300">
  <p class="contents__text">舌は先生のかっこう音館をゴーシュを立っ療たた。また間もなく無理ましたという巨たた。普通たたものんはですたとえば裏のそれどころげのところをは夜通し上手ましたので、おまえまで音楽を飛びつきれのないた。</p>
</div>

見出し・画像・文章用の要素と、それらをラップするdiv要素のみの、非常にシンプルな構造です。

また、要素の順番も見た目の配置とは関係ないので、コンテンツとして重要な順番に並べることができます。

レイアウト(見た目)のことを考えたHTML要素や順番の配置が不要であるという点は、セマンティクスの観点からも理想的なものであると言えるかもしれません。

CSSグリッドレイアウトでの幅や高さの指定には、frなどといった独自の単位が必要になることがあり、これらに苦手意識を持っている方もいらっしゃるかもしれません。しかし、今回の記事では、グリッドに対する幅や高さの指定は行わず、よくある要素の幅・高さの指定だけでレイアウトが作れるようにしました。

ブレイクポイント

ブレイクポイントは@media only screen and ( min-width: 768px )とし、スマホのレイアウトから組み始め、ブラウザの横幅が768pxを超えたところからPCのレイアウトになるよう設定します。

コーディング

レイアウトに関係ない部分

まず、今回のレイアウトと関係のない部分は今回のメインテーマではないので、先にスタイルを書いておきます。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
}

.contents__title {
  font-size: 24px;
}

.contents__image {
}

.contents__text {
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

テキストの部分に何やらいろいろと書いてあるのは、3行以上になったら、3点リーダーを表示してそれ以降は非表示にするためのもので、今回は詳しく説明しません。

この時点の見た目は以下です。当然ですが、すべての要素が縦に並びます。

全ての要素が縦に並ぶ

グリッドコンテナーを定義

外側の.contentsdisplay: grid;を指定することで、これがグリッドコンテナーとなります。また、グリッドコンテナーの直下の要素(今回の場合は見出し、画像、テキストの3つ)が自動的にグリッドアイテムとなります。

ざっくりいうと、グリッドコンテナーが定義した場所にグリッドアイテムを配置できるようになる、という感じです。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;}

.contents__title {
  font-size: 24px;
}

.contents__image {
}

.contents__text {
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

グリッドコンテナー・グリッドアイテムを定義しただけでは見た目の変化はほとんどありません。しかしGoogle Chromeのディベロッパーツールのインスペクターで確認すると、指定前と異なり、点線で要素が囲われていることを確認できます。

ディベロッパーツールでの変化が確認できる

これで、グリッドレイアウトを作る準備が整いました。

配置する要素にグリッド用の名前をつける

次に、配置をコントロールしたい見出し、画像、テキストの3つの要素に、グリッド用の名前を付けていきます。

CSSに、以下のような追加を行います。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;
}

.contents__title {
  grid-area: title;  font-size: 24px;
}

.contents__image {
  grid-area: image;}

.contents__text {
  grid-area: text;  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

このtitleimagetextといった名前は自由なもので構いません。

この追加を行うと、見た目が崩れますが、いったんそのままにして進めます。

グリッドアイテムの配置を定義する

グリッドコンテナーの要素(.contents)に、grid-template-areasというプロパティを以下のよう指定します。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;
  grid-template-areas:    "title title"    "image text";}

.contents__title {
  grid-area: title;
  font-size: 24px;
}

.contents__image {
  grid-area: image;
}

.contents__text {
  grid-area: text;
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

見た目は以下のようになります。

グリッドアイテムの配置

画像の幅・高さの指定をしていないので、まだ不格好に見えますが、画像とテキストを横並びに配置できているのがわかります。

grid-template-areasの内容の説明です。

"(もしくは')で囲まれた数だけ、行(縦方向の分割)が増えます。grid-template-areas: "〇〇" "〇〇"で2行になります。

また、"(もしくは')で囲まれた中の、半角スペースで区切られた文字の数だけ、列(横方向の分割)が増えます。"〇〇 〇〇"で2列になります。

よって、grid-template-areas: "title title" "image text";で、横2マス、縦2マスの表のような配置を定義できます。

プロパティ内の改行はなくても良いのですが、改行を入れることで、見た目のセルの分割に近い形がコードで表現できます。

見た目のセルの分割に近い形がコードで表現できる

半角スペースの数は1つ以上でいくつでも良いので、スペースの数を調整してよりきれいに見た目を整えることもできます。

※ 余談ですが、 SASSの記法は.scss.sassの2つがあり、.sass記法ではプロパティの中で改行することができずコンパイルでエラーになります。見た目で理解しやすい書き方のため、.sassを採用しているプロジェクトでは不便に感じました。

そして、「配置する要素にグリッド用の名前をつける」で付けたそれぞれの名前を書いていきます。テキストは1行目の1列目と2列目の両方に配置し、画像は2行目の左側、テキストは2行目の右側に配置されるように指定しました。

グリッドアイテムの配置を指定

この時点では、まだ幅や高さは気にしていません。

PCでレイアウトを変化させる

いよいよ、メディアクエリーを使用してPCで別レイアウトを表現します。

CSSに以下のような追加を行います。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;
  grid-template-areas:
    "title title"
    "image text";
}

@media only screen and ( min-width: 768px) {  .contents {    grid-template-areas:    "image title"    "image text";  }}
.contents__title {
  grid-area: title;
  font-size: 24px;
}

.contents__image {
  grid-area: image;
}

.contents__text {
  grid-area: text;
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

ブレイクポイントを超えたところから、グリッドアイテムの配置が以下になるようにgrid-template-areasを上書きします。

グリッドアイテムの配置を変更

結果は以下のようになります。

PCのレイアウト

意図した通り、見出しが画像の横に配置されました。

画像の大きさを指定

次に、画像の大きさを指定します。デザインデータでは、スマホで100×100px、PCで200×150pxの大きさで指定されているとします。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;
  grid-template-areas:
    "title title"
    "image text";
}

@media only screen and ( min-width: 768px) {
  .contents {
    grid-template-areas:
    "image title"
    "image text";
  }
}

.contents__title {
  grid-area: title;
  font-size: 24px;
}

.contents__image {
  grid-area: image;
  width: 100px;  height: 100px;  object-fit: cover;}
@media only screen and ( min-width: 768px) {  .contents__image {    width: 200px;    height: 150px;  }}
.contents__text {
  grid-area: text;
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

object-fit: cover;をつけることで、画像の元ファイルの幅と高さによらず、縦横比を変更することなくぴったり画像を配置できます。

見た目は以下のようになります。だいぶ理想の形に近づいてきました。

画像の大きさを調整

セルの間の余白を調整

このままでは、画像と他の要素がくっついてしまっているので、行間・列間に余白を設けます。

グリッドコンテナーの要素(.contents)にgapプロパティを設定します。半角スペースで区切って、1つ目は行間(縦方向の余白)、2つ目は列間(横方向の余白)となっています。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: grid;
  grid-template-areas:
    "title title"
    "image text";
  gap: 5px 10px;}

@media only screen and ( min-width: 768px) {
  .contents {
    grid-template-areas:
    "image title"
    "image text";
  }
}

.contents__title {
  grid-area: title;
  font-size: 24px;
}

.contents__image {
  grid-area: image;
  width: 100px;
  height: 100px;
  object-fit: cover;
}

@media only screen and ( min-width: 768px) {
  .contents__image {
    width: 200px;
    height: 150px;
  }
}

.contents__text {
  grid-area: text;
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;  
  overflow: hidden;
}

行間列間に余白ができた

要素の間に余白が追加されました。

ディベロッパーツールで確認すると、行と列の配置がどうなっているか、わかりやすいと思います。

ディベロッパーツールの表示

セルの中を縦位置中央に揃える

最後に、それぞれのセル内の要素の縦位置を中央に揃えます。

グリッドコンテナーの要素(.contents)にalign-items: center;を指定します。

align-items: center;は、フレックスボックスレイアウトでも使用することのあるプロパティです。

結果は以下のようになります。

縦位置を中央に揃える

セルの中で、テキストの位置が中央に寄っていることが確認できます。見出しと画像も縦位置が中央に寄っているのですが、このレイアウトでは見た目状の変化は見られません。

これで、PCとスマホで別のレイアウトを表現するCSSが完成しました。

IE対応

IE11に対応する場合、上記のコードを以下のように変更します。

css
.contents {
  max-width: 500px;
  margin: 20px auto;
  display: -ms-grid;
  display: grid;
      grid-template-areas: "title title" "image text";
  gap: 5px 10px;
  -webkit-box-align: center;
      -ms-flex-align: center;
          align-items: center;
}

.contents__title {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  -ms-grid-column-span: 3;
  grid-area: title;
  font-size: 24px;
}

.contents__image {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
  grid-area: image;
  width: 100px;
  height: 100px;
  -o-object-fit: cover;
     object-fit: cover;
}

.contents__text {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
  grid-area: text;
  line-height: 1.5;
  height: 4.5em;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media only screen and (min-width: 768px) {
  .contents {
        grid-template-areas: "image title" "image text";
  }
  .contents__image {
    width: 200px;
    height: 150px;
  }
  .contents__title {
    -ms-grid-row: 1;
    -ms-grid-column: 2;
    -ms-grid-column-span: 1;
  }
  .contents__image {
    -ms-grid-row: 1;
    -ms-grid-row-span: 2;
    -ms-grid-column: 1;
  }
  .contents__text {
    -ms-grid-row: 2;
    -ms-grid-column: 2;
  }
}

。。。めちゃくちゃ複雑になってしまいました(笑)

実は、grid-template-areasをIE11で表示するためには、かなり複雑なベンダープレフィックス付きのプロパティを記述しなければなりません。

CSSを書く環境として、gulpやwebpackを使用している場合は、コンパイルにAutoprefixerを導入して、これらのコードを自動で吐き出してもらうのが良いです。

Autoprefixerの設定は、Qiitaで参考になる記事があります。

Qiita: Autoprefixerが進化してCSS GridのIE 11対応がバリ楽になった(2017年〜2018年)

手書きでCSSを書いている方は、grid-template-areasのIE対応を覚えるよりも、Autoprefixerの使い方を覚える方が簡単で生産的だと思いますので、ぜひこの機会に調べて挑戦してみてください。

終わりに

この記事では、CSSグリッドレイアウトを使って、PCとスマホで別のレイアウトを表現する方法を解説しました。

最終的なコードをご覧になりたい方は、以下のCodePenにアクセスしてください。

完成形のソースコードを見る

記事の内容からもわかるように、CSSグリッドレイアウトはかなり柔軟なレイアウト指定を、HTMLを複雑にすることなく実現できます。

これでもう、実装が大変だからといってデザインを変更してもらう必要はありません。

最後までお読みいただきありがとうございました。