デザインの変更に合わせてHTMLを変えるのではなく、CSSでさまざまなデザインを適用する実装テクニック
Post on:2020年6月16日
デザインの変更に合わせてHTMLを変えるのではなく、HTML(文章構造)を変更せずに、CSSでさまざまなデザインを適用する時に役立つ実装テクニックを紹介します。
CSS GridとFlexboxそれぞれの効果的な使い方、コードの並び順と見た目の並び順を変える方法など、トライアンドエラーの過程も解説されているので、勉強になると思います。
Same HTML, Different CSS
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
- はじめに
- 基本のHTML
- デザインのバリエーション
- バリエーション その1
- バリエーション その2
- バリエーション その3
- バリエーション その4
- バリエーション その5
- デザインの変更に合わせて文章構造を変えてはいけない
はじめに
HTMLを変更せずに、CSSでさまざまなデザインを適用
Evolution of Web Designというプロジェクトを見たことがありますか?
Webページのデザインが1993年から2015年までの間にどのように変化したかを見ることができます。このアイデアはとても刺激的で、私も同じことに挑戦しようと思いました。それは、HTMLコンテンツには一切触れることなく、複数のデザインを適用することです。
この記事では、HTMLコードを変更せずにCSSでさまざまなスタイルを検討する必要があるHTMLコンテンツについて解説しています。なかなか大変そうですよね。
それぞれのデザインのバリエーションに取り組む過程で、深く考察し、解決策を模索していきます。場合によっては、<div>を追加したくなるかもしれません。しかし、この記事ではHTMLは一切変更しません。
その他の要件は、下記の通りです。
- 完全にアクセシブルであること
- レスポンシブ対応
- パフォーマンス
- プログレッシブエンハンスメント
この業界に長くいる人であれば、CSS Zen Gardenというプロジェクトを知っているかもしれません。それに似ていますが、この記事はコンポーネントレベルです。
基本のHTML
通常、Webページやブログにはヘッダがあります。今回用意したヘッダには、タイトル、画像、著者、カテゴリを含んでいます。基本のHTMLは下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 |
<div class="c-entry"> <h1 class="c-entry__title">Tech companies tried to help us spend less time on our phones. It didn’t work.</h1> <img class="c-entry__figure" src="figure.jpg" alt=""> <p class="c-entry__author"><img class="c-avatar" src="shadeed.jpg" alt="">By <a href="#"><span>Ahmad Shadeed</span></a> <a href="#"><span>@shadeed9</span></a></p> <ul class="c-list"> <li class="c-list__item"><a class="c-tag" href="#">Design</a></li> <li class="c-list__item"><a class="c-tag" href="#">Tech</a></li> <li class="c-list__item"><a class="c-tag" href="#">Business</a></li> </ul> </div> |
デザインのバリエーション
課題は、HTMLのコードに触れることなく、ヘッダの5つのバリエーションに取り組むことです。まずは、最終結果をご覧ください。
それでは、1つずつ解説します。
バリエーション その1
バリエーション その1
このデザインのポイントは、デザインにおける要素の並び順がHTMLにおけるマークアップの並び順と異なることです。HTMLでは上から、タイトル、画像、著者、カテゴリの順になっています。
HTMLを可能な限りセマンティックに保つためには、何をすべきでしょう? まずタイトルをどう扱うかです。視覚的にどのように見えるかはあまり考えずに、常にセマンティックにHTMLを記述する必要があります。
まずは、要素にベースのスタイルを簡単に適用します。
簡単にベースのスタイルを適用
現在取り組んでいるデザインでは、カテゴリはヘッダ上部に配置されているので、表示の順番を変更する必要があります。それをするには、Flexboxが最適です。
1 2 3 4 |
.c-entry { display: flex; flex-direction: column; } |
flex-directionのデフォルト値はrowなので、columnにし、子要素が積み重なるようにします。あとは、この子要素の順番を変更するだけです。
参考: flexコンテナ: flex-direction(横並びと縦並び)
1 2 3 |
.c-list { order: -1; } |
これで、カテゴリがタイトルの上に表示されました。
カテゴリをタイトルの上に移動
並び順の次は、垂直方向のスペースに注意する必要があります。要素ごとにマージンを与えることはできるだけ避けたいと思うので、親要素を元にその子要素にマージンを適用するようにします。
1 2 3 4 |
.c-entry > * + * { margin-top: 1rem; outline: solid 2px; } |
これですべての子要素にマージンが適用され、確認用にoutlineでボーダーをつけました。カテゴリ要素も子要素で、margin-topが適用される要素の1つであることに注目してください。親要素を元にした理由は、CSSでHTMLのコードの順番通りにスペースを与えるためです。
垂直方向のスペースを追加
ブルーのエリアが、与えた要素間のスペースです。しかし、カテゴリ要素の上に必要のないスペースがあります。解決策は、すべての要素にmargin-bottomを追加し、著者要素に対してはmargin-bottom: 0;でリセットします。HTMLでは、著者・カテゴリの順番にマークアップされています。
1 2 3 4 5 6 7 8 |
/* c-entry要素のすべての子孫を選択 */ .c-entry > * { margin-bottom: 1rem; } .c-entry__author { margin-bottom: 0; } |
バリエーション その2
バリエーション その2
このデザインは少し難易度が高いです。2カラムに分割され、カテゴリ要素は画像の上中央に配置されています。もちろん、このデザインもHTMLを変更せずに、CSSで実現できます。
最初に思い浮かんだ方法は、CSS Gridです。
CSSで大まかなレイアウトを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.c-entry { display: grid; grid-template-columns: 1fr 0.9fr; grid-gap: 1rem; } .c-entry__title { grid-row: 1 / 2; grid-column: 1 / 2; } .c-entry__figure { grid-row: 1/3; } |
これは初期CSSの結果です。
問題があります、カテゴリ要素は右上に配置する必要あり、タイトルと著者のスペースを狭くする必要があります。
初期CSSの結果
問題はこれだけかと思うかもしれませんが、そうではありません。画像の高さの値が大きい場合、レイアウトが崩れます。
画像の高さが大きいと、レイアウトが崩れる
タイトルと著者のスペースが広がることに注目してください。これはCSS Gridがコンテンツの高さに基づいて行を追加するのが原因です。Gridアイテムのデフォルトの整列はstretchです。これを上書きするために、タイトルと著者にalign-self: start;を追加します。
align-self: start;のアリとナシ
しかし、これでは問題は解決しませんでした。それでもタイトルと著者を互いに積み重ねたいのです。多くの試行錯誤の後、別のテクニックに移行することにしました。
まずは、画像を絶対配置にし、幅を47%に(列が半分ではないので47%です)しました。
1 2 3 4 5 6 7 |
.c-entry__figure { position: absolute; height: 300px; right: 0; width: 47%; object-fit: cover; } |
続いて、ヘッダのコンテナ要素にalign-content: start;を加えます。これでうまくいきました!
カテゴリは画像と重なるように同じく絶対配置します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.c-entry { display: grid; grid-template-columns: 1fr 0.9fr; grid-gap: 1rem; align-content: start; } .c-list { position: absolute; top: 0.5rem; right: 0; width: 47%; justify-content: center; } |
ここで、想定外の問題に気がつきました。画像がコンテナよりも大きな高さで固定された状態で絶対配置されているため、画像がコンテナの外に出てしまいます。
画像がコンテナの外に出てしまう
ヘッダの下に他の要素があると、それらの要素と画像が重なってしまうという問題です。これを解決するには、コンテナにmin-heightを追加し、画像のheightを100%にする必要があります。
1 2 3 4 5 6 7 8 |
.c-entry { min-height: 300px; /* other styles */ } .c-entry__figure { height: 100%; } |
さらに、上記のコードに加えてタイトルの前にあるイエローのバーを追加する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.c-entry__title { position: relative; margin-top: 1rem; /* other styles */ } .c-entry--title:before { content: ""; position: absolute; left: -1rem; top: 0; height: 18px; width: 90%; background: $brand-primary; border-top-right-radius: 10px; border-bottom-right-radius: 10px; } |
最も難しい部分が終わったので、次は小さいスクリーンでどのように表示されるか確認します。「レスポンシブ対応」は、要件の1つです。
表示を小さいスクリーンで確認
上記のデザインを実現するためには、CSS Gridはそのままで、grid-row-gapの利点を利用してスペースを追加します。そうすれば、要素間のスペースにmarginは必要なくなります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.c-entry { display: grid; grid-row-gap: 1rem; } @media (min-width: 550px) { .c-entry { grid-template-columns: 1fr 0.9fr; grid-gap: 1rem; min-height: 300px; align-content: start; } } |
最終的な結果は、下記の通りです。
各スクリーンサイズでの表示をご覧ください。
各スクリーンサイズでの表示
バリエーション その3
バリエーション その3
このデザインはバリエーション その1と要素の並び順が似ていますが、次の点が異なります。
- 画像は半円
- 著者画像の配置が異なる
- 画像の下に全幅のボーダー
まずは、CSS Gridで要素間のスペースとコンテンツの中央揃えを定義します。
1 2 3 4 5 |
.c-entry { display: grid; grid-row-gap: 1rem; justify-items: center; } |
次に、カテゴリ要素を一番上に移動するため、その1と同じようにorderプロパティを使用します。CSS Gridでも同じように動作します!
1 2 3 |
.c-list { order: -1; } |
ここまでの実装
次のステップは、タイトルの幅を制限し、text-alignで中央揃えに配置することです。ビューポートの単位を使用した理由は、ビューポートとコンテンツに比例した動的な幅を定義できるからです。
1 2 3 4 |
.c-entry__title { max-width: calc(25ch + 5vw); text-align: center; } |
著者画像をどうにかしないといけません。これにぴったりなテクニックがあります、昔からあるCSSのポジショニングのテクニックです。
1 2 3 4 5 6 |
.c-entry__author .c-avatar { position: absolute; left: 50%; top: -200%; transform: translateX(-50%); } |
あとは、画像の下のボーダーです。これはどのように処理すればよいと思いますか?
説明のために、モックアップを用意しました。
ボーダーをどう実装すればよいか
著者画像とも重なっているので、著者要素の疑似要素としてボーダーを追加できるか考えてみました。しかし、著者要素は親要素の全幅ではないため、ダメです。
著者要素の疑似要素だと、長さが足りない
長さが足りない理由は、コンテナ要素.c-entryにjustify-items: center;を定義したからです。要素のサイズはコンテンツに合わせて変更されてしまいます。
これを無効にするために、著者要素のjustify-itemsを上書きしてリセットします。
1 2 3 4 |
.c-entry__author { justify-self: normal; /* other styles */ } |
これでコンテナの全幅を横切るボーダーを疑似要素で追加できます。
1 2 3 4 5 6 7 8 |
.c-entry__author:before { content: ""; position: absolute; left: 0; right: 0; top: -1rem; height: 3px; } |
コンテナの全幅を横切るボーダー
バリエーション その4
バリエーション その4
このデザインでは、すべてのコンテンツの下に画像が配置され、コンテンツを読みやすくするために透明のオーバーレイが使用されています。
まずは、画像をスクリーンいっぱいに配置し、疑似要素でオーバーレイを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
.c-entry:after { content: ""; position: absolute; left: 0; top: 0; background: #000; width: 100%; height: 100%; z-index: -1; opacity: 0.4; } .c-entry__figure { position: absolute; left: 0; top: 0; z-index: -1; width: 100%; height: 100%; object-fit: cover; } |
これで下記のようになります。
要素の順番と配置はこれからです。
画像とオーバーレイの実装
最初はCSS Gridで要素を中央に配置しようと思ったのですが、要素間に不要なスペースがあるため、正しく中央に配置するには課題があることが分かりました。
そこで、下記のCSSを試してみました。
1 2 3 4 5 |
.c-entry { display: grid; align-items: center; justify-items: center; } |
このCSSで各要素がどのように水平と垂直方向の中央に配置されるか確認してみてください。正しいように見えるかもしれませんが、望ましい結果ではありません。
一見よさそうだが、少し違う
CSS GridではなくFlexboxを使用すると、状況が変わることがあります。中央揃えはCSS Gridとは異なり、コンテナ全体で行われます。そこで、下記のように記述しました。
1 2 3 4 5 6 |
.c-entry { display: flex; flex-direction: column; justify-content: center; align-items: center; } |
コンテナ全体の中央に配置される
このデザインの場合はFlexboxの方が良いようです。最後の変更で、このデザインは完了しました。最終的には、下記のようになります。
完成
バリエーション その5
バリエーション その5
最後のデザインは一見すると、少し複雑に見えるかもしれません。HTMLが下記のようになっている場合、どのようにして画像以外のカテゴリとタイトルと著者を1つの要素でラップすればよいと思いますか?
1 2 3 4 5 6 |
<div class="c-entry"> <h1 class="c-entry__title"><!-- Title --></h1> <img class="c-entry__figure" src="figure.jpg" alt=""> <p class="c-entry__author"></p> <ul class="c-list"></ul> </div> |
カテゴリとタイトルと著者をラップするdiv要素はありません。これを実現するために、私がどのように考えたか解説します。
まずは、CSS Gridで下記のように実装しました。
CSS Gridで実装
次に、すべての要素をそれらを含む要素の中心と端に位置を揃える必要があります。そのため、justifyとalignプロパティを使用します。
1 2 3 4 5 |
.c-entry { /* other styles */ justify-content: center; align-content: end; } |
これで、カテゴリ、タイトル、著者のそれぞれに疑似要素を追加することができます。疑似要素はそれらの背景として機能します。
1 2 3 4 5 6 7 8 9 10 11 12 |
.c-entry__title:after, .c-entry__author:after, .c-list:after { content: ""; background: #ffe649; position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: -1; } |
疑似要素で背景を追加
これらの各要素にはそれぞれposition: relative;が適用されているため、topプロパティで下に少しずらします。
1 2 3 4 5 6 |
.c-entry__title, .c-entry__author, .c-list { position: relative; top: 130px; } |
最後に、ボックスを角丸にするため、最初と最後の要素にのみborder-radiusを定義します。
1 2 3 4 5 6 7 8 9 |
.c-list:after { border-top-right-radius: 10px; border-top-left-radius: 10px; } .c-entry__author:after { border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } |
完成
orderプロパティを使用しすぎると、ユーザーエクスペリエンスに影響を与える可能性があることに注意してください。このプロパティを使用する場合、フォーカスの順序は視覚的な順序ではなく、HTMLで記述した順序になります。
指摘してくれたGeoffrey Crofteに感謝します。
デザインの変更に合わせて文章構造を変えてはいけない
ヘッダに5つのデザインを用意するのは簡単なことではありませんでした。さらに、それをコーディングするのはさらに大変でした。私は今回のスタディで新しいことを学び、デザインが変更されたときにはHTMLを変更する前に考えることが重要であることを再認識しました。CSSは強力で、驚くほど美しいことを実現できます。それを理解した上で、デザインの変更に合わせてHTMLを変えるのではなく、HTMLをセマンティックに保ちましょう。
コメントや提案があれば、@shadeed9までお願いします。
sponsors