メインコンテンツまでスキップ

あなたが教わってるそのCSSテクニックはもう古い

Development
キーワード
CSS

Xの初学者のポストにて古の手法を教わっている方をよく見かけるので、2024年現在そのCSSテクニックはもう古いってものをいくつか列挙しました。

ブロックのセンタリングに 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-inlinemargin-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:flexgapを設けつつ要素を格子状に並べる場合は「コンテナの横幅からギャップの合計値を引いたものを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: '';
}

insettop right bottom left を一括指定するプロパティで、inset:0top 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:relativeoverflow: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のfillcurrentColorを指定すれば、バリエーションで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で描画した三角形の表示例

See the Pen by tak-dcxi (@tak-dcxi) on CodePen.

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で生成してもらったほうが早いです。

ChatGPTに指示をして星をclip-pathで描いてもらう例のスクリーンショットです。

正方形や円形を描くなら aspect-ratio:1 を使いなさい

正方形や円形を描く時、よく教わるのがwidthheightそれぞれの値に同じ数値を指定するやり方です。

.square {
width: 40px;
height: 40px;
}

このやり方では値を変更する際にwidthheightそれぞれの値を変更する必要が出てきます。CSS変数を使用すれば変数の値を変えるだけで済みますが、そのぶん記述量は増えます。

.square {
--size: 40px;
width: var(--size);
height: var(--size);
}

aspect-ratio:1であればwidthheightどちらかの値を指定するだけで実現できます。

.square {
width: 40px;
aspect-ratio: 1;
}

また、レスポンシブ対応などにおいて親要素の横幅を基準とした相対指定(%)で正方形を描く場合、widthheightで実現するのは困難ですが、aspect-ratio:1であれば親要素の横幅を基準とした相対指定でも正方形や円形を描くことができます。

相対指定で円形を描くサンプル

See the Pen by tak-dcxi (@tak-dcxi) on CodePen.

画面いっぱいのメインビジュアルを実装するなら高さの値に 100svb(100svh) を使いなさい

前提として、画面いっぱいのメインビジュアル(ヒーローイメージ)を実装する際に100vhを指定するとiOSではアドレスバーの高さを含んでしまうためはみ出てしまいます。

数年前にこの問題をJavaScriptで解決する記事を投稿しましたが、このやり方も現在は利用する必要はありません。

現在では100svb(100svh)でアドレスバーの高さを含まない画面の高さを取得できるので、基本的にはこれを指定すれば問題ありません。

論理的指定
.mainvisual {
min-block-size: 100svb;
}
物理的指定
.mainvisual {
min-height: 100svh;
}

横書き表示のみであれば100svhで良いですが、縦書き対応の場合はブロックサイズを基準とする100svbを指定しましょう。

また、コンテンツが少ない時にフッターを最下部に固定する実装を行う際も同様です。bodymin-block-size: 100svb を指定しておくといいでしょう。ちなみに現在では position:sticky を使えば display:flexdisplay:grid を指定したラッパーを用意しなくとも固定フッターを実装できます。

body {
min-block-size: 100svb;
}
.footer {
position: sticky;
inset-block-start: 100%;
}

この記事も数年後には古のものになっている可能性が高いので、実装者は常に知識をアップデートしておくことをオススメします。

Share

本文上部へ戻る