[CSS]「display: contents;」がすごい便利!ラッパーを使った実装が大きく変わるこれからのテクニック
Post on:2021年9月4日
例えばカードで、見出しが1行・3行、本文の量が多かったり少なかったりする場合、それぞれの高さを揃えるのは非常に難しく、かなりトリッキーな実装が必要でした。もしくは、JavaScriptを使用しなくてはできなかった実装です。
こういったレイアウトをセマンティックなHTMLで実装できるようになるdisplay: contents;の基礎知識と使い方を紹介します。
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
【更新】
2021/9/4: 現在の環境に合わせて、内容をアップデートしました。
- まずはおさらい、CSSのボックス モデル
- 「display: contents;」を使用すると、どうなるか
- 「display: contents;」について詳しく解説
- 「display: contents;」はなぜ有用なのか
- 「display: contents;」のサポートブラウザ
まずはおさらい、CSSのボックス モデル
今までにも何回か解説してきましたが、ドキュメントツリーのすべての要素は長方形のボックスで構成されています。
広義において、この「長方形のボックス」は2つのセクションで構成されています。
1つ目は、パディング・ボーダー・マージンのエリアで構成された実際のボックスです。2つ目はボックスのコンテンツで、コンテンツ エリアと呼ばれます。
CSSのボックス モデル
CSSのdisplayプロパティを使用すると、このボックスとその子供たちがページ上でどのように描画されるか、定義することができます。
例えば、ボックスをinlineにすると、テキストのように兄弟として配置できます。また、tableにすると、テーブルのように振る舞うことができます。absoluteにすると、ボックスをz軸に配置することさえ可能です。
マークアップで定義された要素がボックスを生成するかどうかを定義するdisplayプロパティには、2つの値しかありません。値をnoneにすると、ボックスまたはそのコンテンツが表示されなくなります。値をcontentsにすると、ボックスのコンテンツは通常通り描画されますが、周囲のボックスは完全に省略されます。
「display: contents;」を使用すると、どうなるか
display: contents;が使用された時に何が起こるのか理解する最も簡単な方法は、要素の開始タグと終了タグがマークアップから省略されていることを想像することです。
W3Cの仕様によると、
For the purposes of box generation and layout, the element must be treated as if it had been replaced in the element tree by its contents
ボックス生成とレイアウトの目的において、要素は要素ツリー内のコンテンツで置き換えられたかのように扱われなければならない。
実際の例を見てみましょう。まずは、マークアップです。
1 2 3 4 |
<div class="outer"> I’m some content <div class="inner">I’m some inner content</div> </div> |
これに、スタイルを適用します。
1 2 3 4 5 6 7 8 9 10 |
.outer { border: 2px solid lightcoral; background-color: lightpink; padding: 20px; } .inner { background-color: #ffdb3a; padding: 20px; } |
ページ上では通常、下記のように表示されます。
ページ上での表示
.outer要素に、display: contents;を追加すると、下記のように表示されます。
.outer要素に、display: contents;を追加
視覚的に言うと、上記の結果は.outer要素の開始タグと終了タグが省略されたマークアップで期待される結果とまったく同じになります。
HTMLにすると、下記のような感じです。
1 2 |
I’m some content <div class="inner">I’m some inner content</div> |
「display: contents;」について詳しく解説
このプロパティは一見、単純明快そうに見えますが、多くの知っておくべきケースと特定のビヘイビアがあります。
display: contents;はページ上に視覚的に描画されるボックスにのみ影響します。ドキュメント内のマークアップには影響しません。
「display: contents;」が要素の属性に与える影響
要素がdisplay: contents;に置き換えられた場合、それに適用されている属性はどうなるでしょうか?
この置換はほとんどの場合、視覚的にのみ行われるため、属性を使用して要素を選択したり、ターゲットにしたり、インタラクションさせることは可能です。例えば、要素をidでターゲットにする場合には、aria-labelledbyを使用して要素を参照します。
1 2 |
<div id="label" style="display: contents;">Label here!</div> <button aria-labelledby="label"><button> |
しかし、これが正しく動作しないことが分かったのは、フラグメント識別子を使用して要素にナビゲートできなくなったことです。
1 2 3 4 5 6 |
<div id="target" style="display: contents;">Target Content</div> <script> window.location.hash = "target"; // => Nothing happens </script> |
「display: contents;」がJavaScriptのイベントに与える影響
これまで説明したように、display: contents;は適用された要素をターゲットにできます。実際、display: none;が適用された要素をターゲットにすることはできますが、インタラクションができないためイベントはトリガーされません。しかし、display: contents;が適用された要素のコンテンツはまだ目に見えるため、そのコンテンツを通して要素とやりとりすることができます。
例えば、要素のクリックに対してイベントリスナーを設定し、thisで値をログに記録すると、外部要素はドキュメント内にまだ存在するため、その要素を取得します。
1 2 3 4 5 6 7 8 |
<div class="outer">I’m some content</div> <script> document.querySelector(".outer").addEventListener("click", function(event) { console.log(this); // => <div class="outer"></div> }); </script> |
「display: contents;」が疑似要素に与える影響
display: contents;が適用された要素の擬似要素は、その子要素の一部とみなされるため、通常通り表示されます。
1 2 3 4 5 6 7 |
<style> .outer { display: contents; } .outer::before { content: "Before" } .outer::after { content: "After" } </style> <div class="outer">I’m some content</div> |
このマークアップは、下記のように表示されます。
期待通りに表示
「display: contents;」がフォーム要素、画像や置換された要素に与える影響
置換された要素といくつかのフォーム要素はdisplay: contents;が適用されると、通常とは異なる動作をします。
- 置換された要素
- 置換された要素とは画像などの要素で、その外見とボックスは外部リソースによって制御されます。このような要素のボックスを削除するのは、実際にはボックスが完全にはっきりしていないため意味がありません。
- これらの要素の場合、display: contents;はdisplay: none;とまったく同じように機能します。要素の全体とコンテンツはページ上に描画されません。
- フォーム要素
- フォーム要素の多くは単一のボックスで構成されているのではなく、いくつかの小さな要素で構成されています。置換された要素と同様に1つのボックスではないため、ボックスを削除するのは意味がありません。
- したがって、<select>, <input>, <textarea>などのフォーム要素の場合、display: contents;はdisplay: none;と同じように機能します。
- 参考: display: contents;が異なって動作する要素の一覧
「display: contents;」がボタンやリンクに与える影響
<button>要素と<a>要素は、display: contents;で特別な動作はしません。ただし、このルールは確定ではない可能性があるため、どのように影響するかを知っておくことは大切です。
- ボタン
- ボタンは、いくつかの小さな要素で構成されるフォーム要素の1つではありません。そのため、display: contents;は周囲のボックスを削除するだけで、ボタンのコンテンツは表示された状態になります。
- ボタンをフォーム内で使用する場合は、ボタンをクリックするとフォームが送信されますが、これまで説明したように、ボタン上のイベントリスナーは正常に機能します。
- リンク
- リンクは、周囲のボックスが視覚的に取り除かれ、リンクのコンテンツが残っているという点で同じことが当てはまります。属性は一般的にこのCSSルールの影響を受けないため、リンクは正常に機能し、通常どおりナビゲートするために使用できます。
「display: contents;」はなぜ有用なのか
わたし達は今まで、HTMLをセマンティックにし、CSSでスタイルするために使用しなければなりませんでした。そのため、ラッピングを目的のために余分な要素が多すぎたり、直接の兄弟スタイルを可能にする要素が少なすぎたりする場合もありました。兄弟スタイルについては今のところ、直接の兄弟要素で作業する必要があるCSS Gridの導入に関係があります。
例えば、下記のレイアウトを見てください。
2つのカードがあり、それぞれに見出し・パラグラフ・フッタが配置されています。ここで望むのは、それぞれの量にかかわらず、2つのカードのそれぞれを同じ高さにすることです(例えば、見出しは左では1行ですが、右では3行です。見出しの量が異なっても、同じ高さにすることを望みます)。
CSS Gridを使用してこのレイアウトを実装することができますが、各カード内のすべての要素が互いに直接の兄弟にする必要があります。
兄弟関係にするためのHTMLを見てましょう。
1 2 3 4 5 6 7 8 9 |
<div class="grid"> <h2>This is a heading</h2> <p>...</p> <p>Footer stuff</p> <h2>This is a really really really super duper loooong heading</h2> <p>...</p> <p>Footer stuff</p> </div> |
このHTMLにスタイルを適用します。
1 2 3 4 5 6 7 |
.grid { display: grid; grid-auto-flow: column; grid-template-rows: auto 1fr auto; grid-template-columns: repeat(2, 1fr); grid-column-gap: 20px; } |
確かに、この実装でレイアウトを実現できますが、HTMLは構造化されてなく、美しくありません。
ここで、display: contents;の登場です。
各カードのコンテンツを<article>でグループ化し、display: contents;を適用します。ページ上に表示される際には、<article>要素は取り除かれます。
1 2 3 4 5 6 7 8 9 10 11 12 |
<div class="grid"> <article style="display: contents;"> <h2>This is a heading</h2> <p>...</p> <p>Footer stuff</p> </article> <article style="display: contents;"> <h2>This is a really really really super duper loooong heading</h2> <p>...</p> <p>Footer stuff</p> </article> </div> |
意味をなすセマンティックなマークアップが実現でき、レイアウトに適したスタイルを適用させることで、HTMLとCSSのベストを尽くすことができます。
「display: contents;」のサポートブラウザ
2021年9月現在、display: contents;はIEを除くすべての主要ブラウザでサポートされています。
サポートされていないブラウザに使用する場合は、適切なフォールバックを使用します。
1 2 3 4 5 6 7 8 |
article { display: grid; grid-template-rows: 200px 1fr auto; /* 例えば、ヘッダの高さを固定する時 */ } @supports (display: contents) { article { display: contents; } } |
sponsors