あなたが教わってるそのCSSテクニックはもう古い – TAKLOG2024年現在そのCSSテクニックはもう古いってものをいくつか列挙しました。初学者や教える側の方は必見です。
Xの初学者のポストにて古の手法を教わっている方をよく見かけるので、2024年現在そのCSSテクニックはもう古いってものをいくつか列挙しました。
- ブロックのセンタリングに margin を使うなら margin-inline:auto を使いなさい
- 要素を格子状に並べたいなら display:grid を使いなさい
- position:absolute した要素を親要素いっぱいに広げたいなら inset:0 を使いなさい
- 要素を親要素の上下左右中央に配置したいなら次の2つから選びなさい
- スタイリングをロールバックするなら値に revert を使いなさい
- 現在のテキストカラーと同じ色を指定するなら currentColor を使いなさい
- 要素のアスペクト比を保ちたいなら padding-top ではなく aspect-ratio を使いなさい
- 三角形を描くなら border ではなく clip-path を使いなさい
- 正方形や円形を描くなら aspect-ratio:1 を使いなさい
- 画面いっぱいのメインビジュアルを実装するなら高さの値に 100svb(100svh) を使いなさい
ブロックのセンタリングに margin を使うなら margin-inline:auto を使いなさい
marginを使ってブロックのセンタリングを行う際によく教わるのはmargin:0 auto
あるいはmargin:auto
でしょう。従来の書き方
.foo { width: fit-content; margin: 0 auto;}
.bar { width: fit-content; margin: auto;}
一般的に上下のmarginの値がauto
になった際は結果的に0として扱われる(参考文献)ので、margin:auto
でも同じ効果が期待できるため上下の0は不要です。じゃあmargin:auto
でいいじゃんかと思われるでしょうが、この指定だと本来不要な上下のmarginにauto
を指定しているのと同等です。
これがどういう弊害を与えるかというと次のようなケースです。
<div class="stack"> <div>...</div> <div class="child">...</div> <div>...</div></div>
<style> .stack > * + * { margin-top: 1rem; }
.child { max-width: 360px; margin: auto; }</style>
child
要素を最大値360pxとしながらmargin
で中央寄せをし、親要素で子要素同士の間に同じ大きさの余白(1rem)ができるようにするという構成ですが、child
要素の上方向のmargin
に不用意に指定されたauto
が邪魔をしてmargin-top: 1rem;
が上書きされてしまい、結果的に要素間に余白が生じなくなってしまいます。
一方、margin-inline:auto
では要素のインライン方向(横書きの場合は左右)のみのmargin
を一括auto指定し、ブロック方向(横書きの場合は上下)のmargin
には関与しないため上記のような問題は起こらなくなります。🙆♂ Recommended
.stack > * + * { margin-top: 1rem;}
.child { max-width: 360px; margin-inline: auto;}
注意点として、margin-inline
のような論理プロパティは縦書きの場合は上下の中央揃えとなります。横書きのみの場合は問題にはなりませんが、縦書き対応のWebサイトを作成する方は論理プロパティや論理値を優先するコーディングを行ってください。
margin-inline
はmargin-left
およびmargin-right
のショートハンドではありません。
当ブログでは論理プロパティと論理値を優先して実装を行っています。
.baz { inline-size: fit-content; /* 横書きの場合はwidthと同等 */ margin-inline: auto;}
要素を格子状に並べたいなら display:grid を使いなさい
要素を格子状に並べる際にdisplay:flex
(絶滅危惧種としてfloat:left
)でのレイアウトを教わっている方もいるようですが、現在ではdisplay:grid
での指定に比べて記述量が増えるだけでなくデメリットも多いです。flexでの組み方
.grid { display: flex; flex-wrap: wrap;}
.grid > * { flex-basis: calc(100% / 3);}
これだけならdisplay:flex
でも大した記述量ではありませんが、格子状に要素を並べる場合多くはgap
を設けると思います。display:flex
でgap
を設けつつ要素を格子状に並べる場合は「コンテナの横幅からギャップの合計値を引いたものをn等分する」という計算式が必要となります。
.grid { --gap: 20px;
display: flex; flex-wrap: wrap; gap: var(--gap);}
.grid > * { flex-basis: calc((100% - var(--gap) * 2) / 3);}
一方display:grid
であれば特別な計算は不要で、これだけのCSSで実現できます。🙆♂ Recommended
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;}
また、display:grid
であればgrid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
のように子要素の最小幅を決めながら親要素の横幅に合わせてレスポンシブにカラムを切り替えるという柔軟なレイアウトも簡単に実現できます。
display:grid
に敷居の高さを感じている初学者も見受けられますが、このようなシンプルなレイアウトであればdisplay:flex
よりも簡単に扱えます。
position:absolute した要素を親要素いっぱいに広げたいなら inset:0 を使いなさい
疑似要素を要素いっぱいにオーバーレイするといった実装の際、だいたいこのあたりを教わっている方が多いと思われます。
.box::after { position: absolute; top: 0; right: 0; bottom: 0; left: 0; content: '';}
.box::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; content: '';}
現在ではinset
プロパティが普及しており、inset
で置き換えると次のような実装で済みます。🙆♂ Recommended
.box::after { position: absolute; inset: 0; content: '';}
inset
は top
right
bottom
left
を一括指定するプロパティで、inset:0
は top
right
bottom
left
にそれぞれ0を指定しているのと同等になります。
img
要素のように width:100%
height:100%
を明示する必要がある場合もありますが、そこは臨機応変にスタイリングしてください。この場合でも top:0
left:0
をそれぞれ指定する必要はなく、inset:0
のみで問題ありません。
.frame > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;}
要素を親要素の上下左右中央に配置したいなら次の2つから選びなさい
上下左右中央に要素を配置したいのなら多くの場合は次の指定で十分です。
<div class="parent"> <div>...</div></div>
<style> .parent { display: grid; place-items: center; }</style>
child が absolute, fixedの場合
<div class="parent"> <div class="child">...</div></div>
<style> .parent { contain: content; /* 説明あり */ }
.child { position: absolute; inset: 0; margin: auto; }</style>
昔の文献でよく紹介されていたtop:50%; left:50%; translate:-50% -50%;
のような指定は現在ではあまりやる必要はございません。
以下上級者向けの解説です。
contain
はコンテンツの一部を独立したサブツリーとしてブラウザに認識させる「封じ込め」に関する指定をするプロパティで、contain:content
で新しい包含ブロックの生成(position:absolute
におけるrelative
のような役割)、スタッキングコンテキストの生成、要素のはみ出しの抑制(overflow:hidden
に似た役割)を兼ね揃えます。ブラウザにレイアウト、スタイル、描画、およびその組み合わせの再計算を絞ることができるのでパフォーマンス向上も見込まれます。position:relative
とoverflow:hidden
を両方指定するような場合、もしくは片方だけでも指定するような場合はcontain
プロパティを使ったほうがメリットある場合もございます。レイアウトによっては利用できない場合もあるため、詳細はMDNを参照してください。
contain – CSS: カスケーディングスタイルシート | MDN
スタイリングをロールバックするなら値に revert を使いなさい
revert
はプロパティの値をスタイルの変更を行わなければそのプロパティが持っていたであろう値にロールバックする値です。簡単に言えば親から継承された値もしくはブラウザデフォルトのスタイルまでロールバックできます。
例えば普段は消している ul
ol
要素のリストマーカーをある箇所では復活させたい、もしくは a
要素のアンダーラインを復活させたいという場合、revert
を使えば簡単に復活させることができます。🙅♂ Not Recommended
.prose ul { list-style: disc;}
.prose ol { list-style: decimal;}
.prose a { text-decoration: underline;}
🙆♂ Recommended
.prose :where(ul, ol) { list-style: revert;}
.prose a { text-decoration: revert;}
レスポンシブ対応でメディアクエリを使用する際に以前の設定を取り消したくなるような場合も、display:block
のように明示的な値を指定をしてロールバックするのではなく、revert
を使用することで本来持っている値に戻しつつ、revert
と記述することでロールバックしたことをコードで明示できます。
.for-desktop { display: none;}
@media (width > 991px) { .for-desktop { display: revert; }}
メディアクエリ内での比較演算子はiOS Safariではバージョン16.4からのサポートのため、16以上が条件の場合は利用を控えるかPostCSSのプラグインを利用する必要があります。
現在のテキストカラーと同じ色を指定するなら currentColor を使いなさい
SVGのfill
やCSSでアイコンを描画する際のbackground-color
などの配色にcolor
で指定しているものと同じ値を設定している教材を見かけますが、バリエーションや状態変化でテキストカラーが変わるような実装だと記述量が増えたり、それぞれの値を管理する手間も増えます。🙅♂ Not Recommended
.button[data-type='primary'] { color: var(--c-primary);}
.button[data-type='primary'] svg { fill: var(--c-primary);}
.button[data-type='secondary'] { color: var(--c-secondary);}
.button[data-type='secondary'] svg { fill: var(--c-secondary);}
.button:disabled { color: var(--c-disabled);}
.button:disabled svg { fill: var(--c-disabled);}
このケースの場合、SVGのfill
にcurrentColor
を指定すれば、バリエーションでcolor
の値が変わった際にfill
の色も連動して現在のテキストカラーと同色になるため記述量が減り、管理が楽になります。🙆♂ Recommended
.button svg { fill: currentColor;}
.button[data-type='primary'] { color: var(--c-primary);}
.button[data-type='secondary'] { color: var(--c-secondary);}
.button:disabled { color: var(--c-disabled);}
また、border-color
の値にテキストカラーと同色を指定している方をよく見かけますが、border-color
の初期値はcurrentColor
なので現在のテキストカラーと同じ色にしたい場合はわざわざborder-color
を指定する必要はございません。🙅♂ Not Recommended
.foo { color: #000; border-top: 1px solid #000; border-bottom: 1px solid #000;}
🙆♂ Recommended
.foo { color: #000; border-top: 1px solid; border-bottom: 1px solid;}
要素のアスペクト比を保ちたいなら padding-top ではなく aspect-ratio を使いなさい
画像やiframe
要素をアスペクト比を保ちながらレスポンシブする際、かつては padding-top
or padding-bottom
の仕様を利用して行っていました。
.frame { contain: content;}
.frame::after { content: ''; display: block; padding-top: calc(9 / 16 * 100%); /* 16 : 9 に保つ */}
.frame :where(img, video, iframe) { position: absolute; width: 100%; height: 100%; inset: 0; object-fit: cover;}
現在では aspect-ratio
プロパティを使えば同等のことを実現できます。🙆♂ Recommended
.frame { aspect-ratio: 16 / 9; /* 16 : 9 に保つ */}
.frame :where(img, video, iframe) { width: 100%; height: 100%; object-fit: cover;}
aspect-ratio
プロパティのほうが記述量を抑えられるだけではなく、古のテクニックは高さを padding
で保っている都合上、子要素が position:absolute
などで縛られます。aspect-ratio
であれば子要素の position
は縛られないため、柔軟にレイアウトを指定することが可能になります。
三角形を描くなら border ではなく clip-path を使いなさい
三角形をCSSで描く際、かつてはborder
の仕様を利用して描いていましたが、現在は簡単な図形であればclip-path
で描画することが可能です。🙅♂ Not Recommended
.tooltip::after { --size: 16px;
border-left: calc(var(--size) / 2) solid transparent; border-right: calc(var(--size) / 2) solid transparent; border-top: calc(tan(60deg) * calc(var(--size) / 2)) solid;}
🙆♂ Recommended
.tooltip::after { --size: 16px;
width: var(--size); height: calc(var(--size) / 2 * tan(60deg)); clip-path: var(--clip-triangle-bottom);}
clip-path
で描画したほうが記述量が少なく済みますし、サイズ調整も容易です。次のCSS変数をコピペしてグローバルなCSSに適用してください。
:root { --clip-triangle-top: polygon(50% 0, 100% 100%, 0 100%); --clip-triangle-bottom: polygon(0 0, 100% 0, 50% 100%); --clip-triangle-right: polygon(0 0, 100% 50%, 0 100%); --clip-triangle-left: polygon(0 50%, 100% 0, 100% 100%); --clip-triangle-lower-left: polygon(0 0, 100% 100%, 0 100%); --clip-triangle-upper-left: polygon(0 0, 100% 0, 0 100%); --clip-triangle-lower-right: polygon(100% 0, 100% 100%, 0 100%); --clip-triangle-upper-right: polygon(0 0, 100% 0, 100% 100%);}
clip-pathで描画した三角形の表示例新しいウィンドウが開きます
clip-pathで図形を描く際はCSS変数に格納し、変数を参照するようにしてください。clip-path: polygon(0 50%, 100% 0, 100% 100%);
をそのまま埋め込むとコードを見ただけではそれがどのような図形か判断がしにくいです。変数化すれば変数名で判断できますし、再利用も容易になります。🙅♂ Not Recommended
.tooltip::after { --size: 16px;
width: var(--size); height: calc(var(--size) / 2 * tan(60deg)); clip-path: polygon(0 0, 100% 0, 50% 100%);}
🙆♂ Recommended
.tooltip::after { --size: 16px;
width: var(--size); height: calc(var(--size) / 2 * tan(60deg)); clip-path: var(--clip-triangle-bottom);}
今回のケースではcalc(var(--size) / 2 * tan(60deg))
で正三角形を描いています。かつてはCSSで正三角形を描く難易度が高い印象でしたが、現在はCSSで三角関数がサポートされているので描画が容易になりました。16pxの上向き下向きの正三角形を描くならwidth: 16px; height: calc(16px / 2 * tan(60deg));
、左向き右向きのそれを描くならwidth: calc(16px / 2 * tan(60deg)); height: 16px;
が計算式です。覚えておくと役立つかもしれません。
また、簡単な図形であればオンラインジェネレーターで作成するよりもChatGPTで生成してもらったほうが早いです。
正方形や円形を描くなら aspect-ratio:1 を使いなさい
正方形や円形を描く時、よく教わるのがwidth
とheight
それぞれの値に同じ数値を指定するやり方です。
.square { width: 40px; height: 40px;}
このやり方では値を変更する際にwidth
とheight
それぞれの値を変更する必要が出てきます。CSS変数を使用すれば変数の値を変えるだけで済みますが、そのぶん記述量は増えます。
.square { --size: 40px;
width: var(--size); height: var(--size);}
aspect-ratio:1
であればwidth
とheight
どちらかの値を指定するだけで実現できます。
.square { width: 40px; aspect-ratio: 1;}
また、レスポンシブ対応などにおいて親要素の横幅を基準とした相対指定(%)で正方形を描く場合、width
とheight
で実現するのは困難ですが、aspect-ratio:1
であれば親要素の横幅を基準とした相対指定でも正方形や円形を描くことができます。相対指定で円形を描くサンプル新しいウィンドウが開きます
画面いっぱいのメインビジュアルを実装するなら高さの値に 100svb(100svh) を使いなさい
前提として、画面いっぱいのメインビジュアル(ヒーローイメージ)を実装する際に100vh
を指定するとiOSではアドレスバーの高さを含んでしまうためはみ出てしまいます。
数年前にこの問題をJavaScriptで解決する記事を投稿しましたが、このやり方も現在は利用する必要はありません。
現在では100svb(100svh)
でアドレスバーの高さを含まない画面の高さを取得できるので、基本的にはこれを指定すれば問題ありません。論理的指定
.mainvisual { min-block-size: 100svb;}
物理的指定
.mainvisual { min-height: 100svh;}
横書き表示のみであれば100svh
で良いですが、縦書き対応の場合はブロックサイズを基準とする100svb
を指定しましょう。
また、コンテンツが少ない時にフッターを最下部に固定する実装を行う際も同様です。body
に min-block-size: 100svb
を指定しておくといいでしょう。ちなみに現在では position:sticky
を使えば display:flex
や display:grid
を指定したラッパーを用意しなくとも固定フッターを実装できます。
body { min-block-size: 100svb;}
.footer { position: sticky; inset-block-start: 100%;}
この記事も数年後には古のものになっている可能性が高いので、実装者は常に知識をアップデートしておくことをオススメします。