モダンCSSによる絶対配置(position: absolute;)の削減
Post on:2021年9月28日
テキストを画像の上に配置、タグを重ねたい、ヒーローセクションで画像の上にコンテンツを配置、画像のアスペクト比を維持させたい時など、CSSの絶対配置(position: absolute;)を使用することがあります。もちろん、それでうまくいく時はありますが、なんらかの制約があったり、テキストが長いと崩れたりします。
position: absolute;が必要だと思われていた実装で、使用しなくても実装できるモダンCSSのテクニックを紹介します。
Less Absolute Positioning With Modern CSS
by Ahmad Shadeed
- はじめに
- ケース1: カードのオーバーレイ
- ケース2: カードのタグ
- ケース3: ヒーローセクション
- ケース4: display: contents;
- ケース5: カードアイテムの並べ替え
- ケース6: 中央寄せ
- ケース7: 画像のアスペクト比
- ケース8: position: absolute;を使用する方がよい場合
- 終わりに
はじめに
私は絶対配置(position: absolute;)が必要なコンポーネントを実装するたびに「本当に必要?」と自問するのですが、絶対配置を使用する必要がないケースがいくつかあることに気がつきました。このことは非常に興味深いと思ったので、ドキュメント化して共有しようと考えました。
この記事では、絶対配置(position: absolute;)を使用する必要がないケースについて解説します。
ケース1: カードのオーバーレイ
画像の上にテキストを配置したカードがある場合、テキスト(コンテンツ)を画像の上に配置するためにposition: absolute;を使用することがよくあります。これは、CSS Gridでは不要になりました。
参考: 5分で完璧に分かる!CSS Gridの基本的な使い方を解説
参考: CSS Gridでどのように配置されるかをまとめたチートシート
画像の上にテキストを配置したカード
これは、テキストオーバーイメージの典型的な例です。HTMLは下記の通りです。
1 2 3 4 5 6 7 8 9 |
<article class="card"> <div class="card__thumb"> <img src="assets/mini-cheesecake.jpg" alt=""> </div> <div class="card__content"> <h2><a href="#">Title</a></h2> <p>Subtitle</p> </div> </article> |
画像の上にコンテンツを配置するには、.card__contentを絶対配置する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.card { position: relative; } .card__content { position: absolute; left: 0; top: 0; width: 100%; height: 100%; background: linear-gradient(to top, #000, rgba(0, 0, 0, 0) bottom/100% 60% no-repeat; padding: 1rem; } |
上記の例でposition: absolute;を使用することは悪いことではありませんが、もっと簡単な方法で実装しませんか?
その方法を解説します。
最初のステップは、カードコンポーネントにdisplay: grid;を追加することです。ここでは、columnsやrowsを定義する必要はありません。
1 2 3 4 5 6 7 8 9 10 |
.card { position: relative; display: grid; } .card__content { display: flex; flex-direction: column; justify-content: flex-end; } |
カードコンポーネントにdisplay: grid;を追加
デフォルトで、CSS Gridはコンテンツに基づいて行を自動的に作成します。このカードでは、メインの要素が2つあるので、コンテンツの行が2つになります。
コンテンツと画像を重ねるには、両方を最初のグリッドエリアに配置する必要があります。
1 2 3 4 5 |
.card__thumb, .card__content { grid-column: 1/2; grid-row: 1/2; } |
さらに簡単にできます、grid-areaのショートハンドで定義します。
1 2 3 4 |
.card__thumb, .card__content { grid-area: 1/2; } |
コンテンツと画像を最初のグリッドエリアに配置
最後に、grid-area: 1/-1;を使用することもできます。-1はグリッドの最後の列や行を表すため、常に列や行の最後にまたがることになります。
ケース2: カードのタグ
ケース1に基づいて拡張し、カードの左上にタグを追加したいと思います。タグを実装するには、grid-area: 1/-1;を使用する必要があります。しかし、タグがカード全体を覆ってしまうのは望ましくありません。
カードのタグ、左: 期待する実装、右: 全体が覆われてしまう
タグが全体を覆わないようにするにはタグの両端を揃え、コンテナの開始位置に揃えるように定義するだけです。
1 2 3 4 5 |
.card__tag { align-self: start; justify-self: start; /* Other styles */ } |
これでposition: absolute;を使用せずに、親よりも上に位置するタグを実装できます。
See the Pen
Less absolute - Card by Ahmad Shadeed (@shadeed)
on CodePen.
ケース3: ヒーローセクション
ヒーローセクションで使用する画像の上にコンテンツを重ねるのも最適な実装例です。
画像の上にコンテンツを配置
カードの例と似ていますが、コンテンツ自体のスタイルが異なります。モダンな方法を解説する前に、絶対配置を使った実装について少し考えてみましょう。
3つのレイヤーがあります。
- 画像
- グラデーションのオーバーレイ
- コンテンツ
3つのレイヤー
これを実装するにはいくつかの方法があります。画像が純粋に装飾目的であれば、background-imageを使用できます。そうでない場合は、<img>要素を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
.hero { position: relative; min-height: 500px; } .hero__thumb { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; } /* オーバーレイ */ .hero:after { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; background-color: #000; opacity: 0.5; } .hero__content { position: absolute; left: 50%; top: 50%; z-index: 1; transform: translate(-50%, -50%); text-align: center; } |
このようにして、ヒーローセクションを絶対配置で実装できます。しかし、もっと良い方法があります。それでは、モダンな方法を解説しましょう。
まず、ヒーロー要素にdisplay: grid;を追加する必要があります。次に、カードコンポーネントと同じテクニックを適用します。つまり、すべての直接の子要素にgrid-area: 1/-1;を適用します。
前述のカードと同じテクニックを適用
残念ながら、.hero__thumbを実際に機能させるためには、ヒーローセクションに固定のheightを定義する必要があります。(height: 100%;の子要素は、その親がmin-heightではなく明示的な固定高さを持つ必要があります)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
.hero { display: grid; height: 500px; } .hero__content { z-index: 1; /* [1] */ grid-area: 1/-1; display: flex; flex-direction: column; margin: auto; /* [2] */ text-align: center; } .hero__thumb { grid-area: 1/-1; object-fit: cover; /* [3] */ width: 100%; height: 100%; min-height: 0; /* [4] */ } .hero:after { content: ""; background-color: #000; opacity: 0.5; grid-area: 1/-1; } |
番号付きのCSSは重要なので、解説します。
- gridやflexアイテムにz-indexを使用できます。position: relative;を追加する必要はまったくありません。
- .hero__contentはgridアイテムなので、margin: auto;を使用すると、水平方向と垂直方向の中央に配置されます。
- 画像を扱うときは、object-fit: cover;を含めることを忘れないでください。
- 大きな画像を使用する場合に備えて、ヒーロー画像にmin-height: 0;を定義しました。これはCSS Gridにheight: 100%;を強制的に尊重させ、画像がヒーローセクションよりも大きくならないようにするためです。
CSS Gridの最小コンテンツサイズについての記事で詳しく説明しています。
よりクリーンなソリューションになったことに気がつきましたか?
See the Pen
Less absolute - Hero Section by Ahmad Shadeed (@shadeed)
on CodePen.
ケース4: display: contents;
これはdisplay: contents;を初めて使用するときの例だと思います。コンテンツと画像がある場合を考えてみましょう。
コンテンツと画像を配置
まずは、HTMLです。
1 2 3 4 5 6 7 8 |
<div class="hero"> <div class="hero__content"> <h2><!-- Title --></h2> <p><!-- Desc --></p> <a href="#">Order now</a> </div> <img src="recipe.jpg" alt=""> </div> |
ここまでは何も問題ありません。スマホでは、下記のようなレイアウトにしたいと考えています。
スマホでの表示
画像が見出しと説明文の間に配置されていることに注目してください。これはどのように実装すればよいのでしょうか? 最初は、HTMLを下記のように変更すべきだと思うかもしれません。
1 2 3 4 5 6 7 8 |
<div class="hero"> <div class="hero__content"> <h2><!-- Title --></h2> <img src="recipe.jpg" alt=""> <p><!-- Desc --></p> <a href="#">Order now</a> </div> </div> |
スマホでは、期待通りに機能します。デスクトップでは、<img>を絶対配置で配置する必要があります。この方法でも問題はありませんが、display: contents;を使ったもっと簡単な方法があります。
最初のHTMLに戻りましょう。
1 2 3 4 5 6 7 8 |
<div class="hero"> <div class="hero__content"> <h2><!-- Title --></h2> <p><!-- Desc --></p> <a href="#">Order now</a> </div> <img src="recipe.jpg" alt=""> </div> |
コンテンツが.hero__contentに内包されていることに注目してください。<h2>, <p>, <a>が<img>と直接兄弟になるようにブラウザに伝えるにはどうすればよいでしょうか?
display: contents;はそれが可能です。
1 2 3 |
.hero__content { display: contents; } |
このCSSを追加することで、.hero__content要素は隠れたゴーストになりました。ブラウザはHTMLを下記のように解析します。
1 2 3 4 5 6 |
<div class="hero"> <h2><!-- Title --></h2> <p><!-- Desc --></p> <a href="#">Order now</a> <img src="recipe.jpg" alt=""> </div> |
CSSでやるべきことは下記の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.hero { display: flex; flex-direction: column; } .hero__content { display: contents; } .hero h2, .hero img { order: -1; } |
display: contents;は正しく使用すれば、数年前は不可能だったことを実現できる強力なテクニックです。
もちろん、デスクトップのスタイルも定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@media (min-width: 750px) { .hero { flex-direction: row; } .hero__content { display: initial; } .hero h2, .hero img { order: initial; } } |
実際の動作は、下記をご覧ください。
See the Pen
Less absolute - display:contents; by Ahmad Shadeed (@shadeed)
on CodePen.
ケース5: カードアイテムの並べ替え
カードコンポーネント内のアイテムの並べ替え
カードの一番上にタイトルを配置するバリエーションを用意しました。それでは、HTMLを見てみましょう。
1 2 3 4 5 6 7 8 |
<article class="card"> <img src="thumb.jpg" alt=""> <div class="card__content"> <h3>Title</h3> <p>Description</p> <p>Actions</p> </div> </article> |
ラッパーの直下に画像とコンテンツの2つの直接の子要素があることに注目してください。このHTMLを使用して、タイトルの<h3>を一番上に配置するにはどうすればよいでしょうか?
絶対配置で実装できます。
1 2 3 4 5 6 7 8 9 10 11 |
.card { position: relative; padding-top: 3rem; /* タイトルのスペースに対応 */ } .card h3 { position: absolute; left: 1rem; top: 1rem; } |
ただし、タイトルが長すぎると、この実装は失敗する可能性があります。
右: タイトルが長いと表示が崩れる
これは、タイトルが通常のフローから外れているために起こることで、ブラウザはタイトルの短さや長さを実際には気にしていません。display: contents;を使用すると、はるかに優れた結果が得られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.card { display: flex; flex-direction: column; padding: 1rem; } .card__content { display: contents; } .card h3 { order: -1; } |
これにより、すべての子要素を制御し、必要に応じてorderプロパティで順番を変更できるようになります。
タイトルが長い場合でも表示は崩れない
親にpadding: 1rem;を追加したので、まだ少し問題があります。画像が端に寄ってしまいます。これを修正する方法は下記のとおりです。
1 2 3 4 |
.card img { width: calc(100% + 2rem); margin-left: -1rem; } |
ケース6: 中央寄せ
要素の中央寄せというと、「難しくて複雑だ」というジョークをよく目にします。最近では、以前よりも簡単に要素を中央寄せで配置できるようになりました。
もうCSSのトランスフォームでposition: absolute;を使用する必要はありません。例えば、margin: auto;を使用すれば、flexアイテムを水平と垂直方向の両方で中央に配置できます。
1 2 3 4 5 6 7 |
.card { display: flex; } .card__image { margin: auto; } |
中央寄せ
中央寄せの現在主流の5つのテクニックと最も万能で信頼できるテクニックもご覧ください。
ケース7: 画像のアスペクト比
CSSの新しいプロパティaspect-ratioは、すべての主要ブラウザでサポートされています。今までは、ボックスにアスペクト比を適用するためにpaddingハックを使用していたと思います。
paddingハックは、下記のようなCSSです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
.card__thumb { position: relative; padding-top: 75%; } .card__thumb img { position: absolute; left: 0; top: 0; width: 100%; height: 100%; object-fit: cover; } |
aspect-ratioプロパティを使用すると、これを簡単に実装できます。
1 2 3 4 5 |
/* */ .card__thumb { position: relative; aspect-ratio: 4/3; } |
aspect-ratioプロパティについては、下記の記事が参考になります。
- CSS aspect-ratioプロパティの基礎知識、便利な使い方、実装に必要なプログレッシブエンハンスメント
- CSS aspect-ratioプロパティの使い方、レスポンシブやレイアウトシフトで大活躍
ケース8: position: absolute;を使用する方がよい場合
コンテンツがカードのカバーと重なっている場合
例えば、コンテンツ(アバター・名前・リンク)がカードのカバーと重なっている場合です。これを実装するには、2つのオプションがあります。
- カードのカバーにposition: absolute;を使用する
- コンテンツに負のマージンを使用する
どちらのオプションがそのユースケースに適しているかを見てみましょう。
position: absolute;を使用する方法
まず、絶対配置に配置
この方法では、矩形を絶対配置に配置してから、コンテンツにpadding: 1rem;を与えます。
1 2 3 4 5 6 7 8 9 10 11 |
.card__cover { position: absolute; left: 0; top: 0; width: 100%; height: 50px; } .card__content { padding: 1rem; } |
そうすれば、カードのカバーを外しても、CSSを編集・変更する必要がありません。
カードのカバーを外してもCSSは変更する必要がない
負のマージンを使用する方法
この方法では、カードは絶対配置に配置されません。その代わりに、コンテンツは負のマージンで配置します。
1 2 3 4 |
.card__content { padding: 1rem; margin-top: -1rem; } |
カードのカバーがある場合は機能しますが、カバーのないバリエーションがある場合には問題が生じます。
カードのカバーを外すと表示が崩れる
アバターがカード(親要素)から外れていることに注目してください。これを修正するには、CSSを変更して負のマージンを取り除く必要があります。
1 2 3 |
.card--no-cover .card__content { margin-top: 0; } |
結果は、このようなケースではposition: absolute;を使用した方が追加のCSSを記述しなくてすむため、より優れた方法であることが分かりました。
終わりに
参考文献
この記事があなたのお役に立てれば幸いです。
コメントや提案があれば、@shadeed9までお願いします。
sponsors