CSS

2024年6月にXに投稿したCSSテクニックのまとめ

先月からXにCSSテクニックを定期的に投稿しているので、それのまとめです。

この記事は2024年6月時点の内容です。その後、ブラウザの対応状況が次のように変わりました。

  • Anchor Positioningは、Chrome 125に続いてSafari 26とFirefox 147でサポートされました。
  • field-sizingプロパティは、Safari 26.2とFirefox 152でサポートされ、2026年6月にBaseline Newly available(新しく利用可能)になりました。
  • Scroll-driven Animationsは、Safari 26とFirefox 159(2026年10月)でサポートされ、Polyfillを導入しなくても全てのモダンブラウザで動作するようになりました。

また、記事で紹介している当ブログのグローバルナビゲーションとフッターメニューのホバーエフェクト、左下のスクロールインジケーターは、現在の当ブログでは使っていません。

テキストの中央寄せだからといって text-align:center を指定したほうが良いかは考えたほうがいい

テキストの中央寄せだからって`text-align: center`を使ったほうが良いかはよく考えたほうがいい #Web制作 pic.twitter.com/FTzOPr8AmT

ポストを別枠で表示する

和文をtext-align:centerで中央寄せすると複数行になった際に見栄えが悪くなるケースが多いです。

inline-size:fit-contentとmargin-inline:autoでセンタリングすることで、1行の場合は中央寄せ、複数行の場合は左寄せといった実装が可能となります。

h1 {
	inline-size: fit-content;
	margin-inline: auto;
}

英文を text-align:center するなら同時に text-wrap:balance も指定しておくとよい

英文を`text-align: center`するなら`text-wrap: balance`も指定しておくといいぞ🫵 #Web制作 pic.twitter.com/bw6wU9mSfz

ポストを別枠で表示する

text-wrapはテキストの折返しを制御するプロパティで、値にbalanceを指定すると各行の文字数が均等になるように折り返されます。

h1 {
	text-align: center;
	text-wrap: balance;
}
text-wrap:balanceの比較

text-wrap:balanceは新しめのプロパティで、現在では全モダンブラウザでサポートされていますがiOSは17.5からのサポートとなります。とはいえ、非対応の環境でtext-wrap:balanceの指定が悪さをすることはないのでプログレッシブ・エンハンスメントの一環で導入しても問題ないでしょう。

text-wrap: balance

新しく利用可能 2024年5月から

執筆時点(2024年7月)から変わっていません

Chrome
114
Edge
114
Firefox
121
Safari
17.5
iOS Safari
17.5

web-features 3.44.0 のデータ

webstatus.dev で見る

また、text-wrap:balanceはバランスを取るために処理負荷がかかるため、数行(環境によってバラツキあり、Chromeでは6行以下)のテキストブロックにのみ対応しています。そのため主に見出しや短いテキストブロックに指定することになるでしょう。加えてパフォーマンスに影響を与えるため、パフォーマンスをとことん重視する場合は使用を控えることも検討したほうがよいかもしれません。

個人的には日本語の見出しは前述したinline-size:fit-contentとmargin-inline:auto、英語の見出しはtext-align:centerとtext-wrap:balanceで実装することが多いです。

アコーディオンの「+」アイコンは grid-area で重ねて実装する

アコーディオンの「+」「-」アイコンの実装に`position:absolute`も空のspanも必要ない。

ボタンに`display:grid`と`grid-template-columns:1fr auto`を指定して、それぞれの疑似要素を`grid-column:2 / 3` `grid-row:1 / 2`で同じ場所に指定するだけ🫵 #Web制作 pic.twitter.com/4n6415bsuU

ポストを別枠で表示する

アコーディオンの「+」アイコンは疑似要素で実装するケースが多いですが、それぞれの疑似要素を重ねるためにposition:absoluteを使用するよりもgrid-areaで同じカラムに重ねるほうがスマートに実装できます。

アコーディオンの実装例
アイコンの実装例
summary {
	display: block grid;
	grid-template: '. icon' / 1fr 1em;
	column-gap: 1em;
	align-items: center;

	&::before,
	&::after {
		content: '';
		grid-area: icon;
		border-block-end: 1px solid;
	}

	&::after {
		rotate: 90deg;
	}

	&:is([open] > *)::after {
		opacity: 0;
	}
}

ポストではgrid-row:1 / 2となっていますが、grid-row:1で十分です。上記の実装例のコードのようにエリア名を明示したほうが可読性は高くなります。

また、強制カラーモードでもアイコンを表示できるようにborderで「+」を描いています。

テキストを中央寄せ、かつ端にアイコンのデザインのボタンは display: inline-grid で実装する

テキスト中央寄せ+端にアイコンのデザインのボタンコンポーネントは`display: inline-grid`で実装しなさい🫵 #Web制作 pic.twitter.com/OfQqgA3zVP

ポストを別枠で表示する

下のサンプルのようにテキストを中央寄せにして、かつ右端にアイコンのデザインのボタンの実装方法は様々ですが、個人的にはinline-gridで実装するのがスマートだと考えます。

ボタンの実装例
CSSの実装例
.button {
	display: inline grid;
	grid-template-columns: 1fr auto 1fr;
	column-gap: 1em;
	align-items: center;
	inline-size: min(100%, 400px);

	&::before {
		content: ''; /* 空の疑似要素を用意する */
	}

	&::after {
		content: '';
		justify-self: end; /* 右寄せにするために必須 */
		inline-size: 0.5em;
		aspect-ratio: 1;
		border-block-start: 1px solid;
		border-inline-end: 1px solid;
		rotate: 45deg;
	}
}

ポイントはgrid-template-columns: 1fr auto 1frで疑似要素に1fr、ラベルテキストにautoを割り当てる点です。autoカラムはラベルテキストの内容に応じて自動的にサイズ調整され、左右の疑似要素に指定されている1frカラムが等しく余白を分け合います。

fr単位は利用可能な空間を比例配分するものなので、ラベルテキストが増えるにつれて左右の1frカラムが縮小します。特に::before疑似要素は空なので、テキストが折り返されるまでにスペースを圧迫すると完全に潰れるようになります。これによりラベルテキストが複数行になった場合は左寄せのように表示されます。

次の記事がうまくまとまっている印象です。

gridのアイテムに minmax() 関数を使用する際は min() 関数を絡めておく

grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));

これだと親要素の幅が360pxを下回ったときにはみ出して横スクロール出るから

grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr));

のようにmin()関数を絡めておきなさい🫵 #Web制作 pic.twitter.com/sgjQ9I1MOt

ポストを別枠で表示する

grid-template-columnsにminmax()関数を指定する方法はレスポンシブを容易にするため重宝されますが、minmax(360px, 1fr)のように最小値に固定値を指定するとグリッドコンテナ幅が360pxを下回ったときにはみ出してしまいます。

🙅‍♂ Not Recommended
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));

このような問題を回避するために、minmax()関数を使用する際は最小値をminmax(min(<length>, 100%), 1fr)のようにmin()関数と組み合わせて指定するクセをつけておくと良いでしょう。この方法を採用することで、グリッドコンテナ幅が固定値を下回った場合でもアイテムの横幅が100%に調整されるため、レイアウトの崩れを防ぐことができます。

🙆‍♂ Recommended
grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr));

一次元の横並びでも grid を使ったほうが適している場合もある

一次元の横並びだからといって`display:flex`を使ったほうがいいかはよく考えたほうがいい🫵 #Web制作 pic.twitter.com/1f6mblZafv

ポストを別枠で表示する

「一次元のレイアウトはflex、二次元のレイアウトはgridが適している」という一般的な認識がありますが、実際には一次元のレイアウトでもgridを使用したほうが効果的なケースが存在します。

例えばよく見かけるニュースリストの実装においてリストの行頭揃えを行う場合、flexでは各列に固定幅を設定しないと揃えることが困難ですが、subgrid機能を活用できるgridを使用すれば簡単に行頭揃えを実現できます。

ニュースリストの例
CSSの実装例
ul {
	display: block grid;
	grid-template-columns: max-content auto 1fr; /* 日付は改行が好ましくないので`max-content`を指定する */
}

li {
	display: block grid;
	grid-template-columns: subgrid;
	grid-column: 1 / -1;
	column-gap: 24px;
	padding-block: 32px;
	border-block-end: 1px solid #dadada;
}

もしもli要素にborderやpaddingなどの装飾が不要な場合は、li要素にdisplay:contentsを指定することでsubgridを使用せずとも行頭揃えが可能です。

前述したボタンやアコーディオンのアイコンの配置テクニックのように一次元の横並びでもgridを使ったほうが適しているケースは他にも存在するため、安易にflexを使用するのではなく、まずはgridの使用を検討し、Clusterレイアウトのようにflexの方が向いてそうなケースならflexを使うといった思考を持っておくと良いかもしれません。

Anchor Positioningでカードの背景をホバーで追従させる

カードの背景をホバーで追従させる方法。
現代ではJS不要でこれだけの記述量で実現できます🫵

サンプル: https://t.co/vX6D5Z8QsX #Web制作 pic.twitter.com/VkPSXe3P9B

ポストを別枠で表示する

実装例のようなカードの背景をホバーで追従させる方法、かつてはJSを使用しないと実装ができませんでしたが、最近Chromeで導入されたAnchor Positioningを使用すればCSSのみで実現可能です。

Anchor Positioningは主にツールチップやPopover APIでの位置調整に用いられるイメージがありますが、こういったホバーエフェクトにも流用可能です。

背景が追従するカードコンポーネントの例
.card-wrapper {
	position: relative;

	&::after {
		content: '';
		position: absolute;
		inset: anchor(--card start);
		z-index: -1;
		inline-size: anchor-size(--card inline);
		block-size: anchor-size(--card block);
		background-color: var(--_background);
		transition-duration: 0.3s;
		transition-property: inset, inline-size, block-size;
	}
}

.card {
	@supports not (anchor-name: --a) {
		transition: background-color var(--_duration);

		&:has(:any-link:hover) {
			@media (any-hover: hover) {
				background-color: var(--_background);
			}
		}
	}

	&:is(:hover, :has(:focus-visible)) {
		anchor-name: --card;
	}
}

Anchor Positioningは現在ではChromeおよびEdgeのみのサポートですが、Anchor Positioningの指定が非対応の環境で悪さをすることはないためプログレッシブ・エンハンスメントの一環で導入しても差し支えないでしょう。実装例では@supports機能クエリを使用してAnchor Positioning非対応のブラウザでは.cardの背景色を表示させるフォールバックを行っています。

当ブログのグローバルナビゲーションおよびフッターメニューのホバーエフェクトにもAnchor Positioningが使用されています。

計算を伴う値を指定する際は答えではなく計算式を指定する

🙋「私は`rem`や`vw`を指定するときに電卓を叩いて計算します!」
🤖「計算式をそのまま`calc()`内で指定すればいいのに…」

✅後からコードを見返したときにどうしてその値になったのか?が分かりやすい🫵
✅値の変更が起こってもわざわざ計算をし直す必要がない🫵 #Web制作 pic.twitter.com/RhmVctnwmw

ポストを別枠で表示する

remやvwのように計算を伴う値を指定する際は解答を指定するのではなく、その答えに至る計算式をそのままcalc()関数内に記述することを推奨します。値の変更が必要になった場合にわざわざ計算をし直す必要はありませんし、後からコードを見返したときにどうしてその値になったのか?が分かりやすくなります。

僕はグローバルスコープのCSS変数にそれぞれのデザインカンプのフレームの大きさとベースのフォントサイズ、それらを基準としたpx→rem、px→dvi(dvw)変換用の計算式を格納しておくことが多いです。

CSSの実装例
:root {
	--layout-width-min: 375; /* SPのデザインカンプの横幅 */
	--layout-width-max: 1280; /* PCのデザインカンプの横幅 */
	--base-font-size: 16; /* デフォルトのフォントサイズ */

	/* カスタムプロパティに計算式を格納すればそれ以降の指定が簡潔になる */
	--fluid-ratio-min: calc(1 / var(--layout-width-min) * 100dvi);
	--fluid-ratio-max: calc(1 / var(--layout-width-max) * 100dvi);
	--rem: calc(1rem / var(--base-font-size));
}

.container {
	/* デザインカンプの幅が375pxで、要素の内側の余白が32px */
	padding-inline: calc(32 * var(--fluid-ratio-min));
}

.container {
	/* デザインカンプの幅が1280pxで、要素の内側の余白が40px */
	padding-inline: calc(40 * var(--fluid-ratio-max));
}

h1 {
	/* 24pxをremに変換 */
	font-size: calc(24 * var(--rem));
}

このようなアプローチを採用することで記述量は増えるもののコードの意図がより明確になり、将来の修正や調整が容易になっています。ついでにSassの@functionなども不要になります。(Tailwindを使用する場合はプラグインを自作します)

ただしclamp()関数の推奨値の計算は複雑なため、メタ言語やフレームワークを使用しないでCSSを書く場合はVSCodeのユーザースニペットに次のような指定を登録して呼び出すようにしています。

ユーザースニペットの例
{
	"clamp": {
		"prefix": "clamp",
		"body": [
			"--_clamp-min: $1;", // 最小値
			"--_clamp-max: $2;", // 最大値
			"--_clamp: clamp(var(--_clamp-min) * var(--rem), (var(--_clamp-min) - (var(--_clamp-max) - var(--_clamp-min)) / (var(--layout-width-max) - var(--layout-width-min)) * var(--layout-width-min)) * var(--rem) + (var(--_clamp-max) - var(--_clamp-min)) / (var(--layout-width-max) - var(--layout-width-min)) * 100dvi, var(--_clamp-max) * var(--rem));",
			"$3: var(--_clamp);"
		]
	}
}

Font-size Clamp Generator(新しいタブで開く)のようなジェネレーターもありますが、値の追加や変更をする場合に一々ジェネレーターを開くのは面倒くさいのでこのようなアプローチを取っています。

意図が分かりにくい数値を指定する場合はCSS変数で数値の意味を説明する

CSS書く人はアウターかインナーどちらかの角丸を基準として、基準とした`border-radius`と`padding`をローカルスコープの変数にして、基準でない角丸はcalc()を経由して指定するんだぞ🫵

でないと基準でない角丸は見返したときになんでこの数値やねんってなるからな🫵あと変更にも弱い #Web制作 https://t.co/ruiSpqxfzz pic.twitter.com/kwujqfrICZ

ポストを別枠で表示する

CSSの構造上key:valueの形で自然と意味が伝わる場合は必ずしも全ての数値をCSS変数で説明する必要はありませんが、数値の意図が分かりにくい場合はローカルスコープのCSS変数を活用して数値の意味を説明するようにしています。

例えばポストのような角丸のデザインを実装する場合はアウターかインナーのどちらかのborder-radiusの値を基準として選び、そのborder-radiusとpaddingをローカルスコープの変数に定義します。基準としない方の角丸はcalc()関数を使用して基準値から導出するようにします。

.card {
	--_padding: 8px;
	--_outer-radius: 24px;
	--_inner-radius: calc(var(--_outer-radius) - var(--_padding));

	padding: var(--_padding);
	border-radius: var(--_outer-radius);

	& > * {
		border-radius: var(--_inner-radius);
	}
}

フォームの入力欄のユーザー体験を向上させる field-sizing:content は導入したほうがいい

フォームの入力欄のユーザー体験を向上させる`field-sizing: content`は導入したほうがいい🫵

※ChromeとEdgeのみのサポートですが、非対応のブラウザで`field-sizing: content`の指定が悪さすることはないのでプログレッシブエンハンスメントの一環として導入する価値はあります #Web制作 pic.twitter.com/7UFEcAjkEI

ポストを別枠で表示する

以前投稿した記事でも紹介しましたが、入力欄の大きさを内容に応じて調整することができるfield-sizing:contentは現状Google ChromeとMicrosoft Edgeのみの対応ではあるもののプログレッシブ・エンハンスメントの一環として導入する価値はあります。

CSSの実装例
textarea {
	--_min-rows: 5; /* デフォルトの行数 */
	--_max-rows: 20; /* 最大行数 */
	--_padding: 1em;

	box-sizing: border-box;
	inline-size: 100%;
	min-block-size: calc(var(--_min-rows) * 1lh + var(--_padding) * 2);
	max-block-size: calc(var(--_max-rows) * 1lh + var(--_padding) * 2);
	padding: var(--_padding);
	field-sizing: content;

	@supports (field-sizing: content) {
		resize: none; /* field-sizing有効時にはリサイズ機能を無効にする */
	}
}

ラジオボタンやチェックボックスを装飾する場合は input 要素に直接スタイルをあてる

現代ではチェックボックスやラジオボタンに独自のデザインを適用する場合にinputを隠して空のspan要素等を用意する必要はなく、`appearance:none`しておけばinput要素にスタイル直書きで問題ないです🫵(疑似要素も使用できます) pic.twitter.com/Pdj2I4eRyz

ポストを別枠で表示する

ラジオボタンやチェックボックスの装飾を求められるケースは多くありますが、accent-colorで対応できないそれらのデザインを実装する場合、input要素を非表示にして空のspan要素やラッパーのlabel要素の疑似要素で代替する方法が一般的でした。

しかし、現代ではtype="radio"およびtype="checkbox"に限り、input要素にappearance:noneを指定することで疑似要素が使用可能な空の要素として扱えるようになり、直接スタイリングが可能になります。

[type='checkbox' i] {
	display: inline flow-root; /* 幅と高さの指定を効くようにするため`inline-block`などの値を指定する */
	appearance: none;
	/* チェックボックスのスタイル */

	&:checked::before {
		/* チェックボックスがアクティブ時のスタイル */
	}
}
  • has()や隣接セレクタを使用する必要がないため記述量を減らせる
  • input要素の非表示の方法によってはTab移動での選択が不可能になってしまうが、そういった事故を防げる
  • input要素のフォーカスリングが可視化される

といった理由から極力inputを隠す従来の実装は避けたほうがいいでしょう。

注意点としては縦横比が決まっている場合でもinputにaspect-ratioを指定するとSafariで潰れて表示されてしまうため、縦幅と横幅は明示しておくことを推奨します。

🙅‍♂ Not Recommended
[type='checkbox' i] {
	inline-size: 1lh;
	aspect-ratio: 1;
}
🙆‍♂ Recommended
[type='checkbox' i] {
	inline-size: 1lh;
	block-size: 1lh;
}

以下は前回の記事で触れたinput[type="checkbox"]要素のswitch属性を使用したスイッチUIの実装例です。強制カラーモード対応周りなど、前回の記事の実装例より少しアップデートしています。

スイッチUIの実装例
スイッチUIの実装例
[switch] {
	--_size-unit: 1lh;
	--_background: #dadada;
	--_foreground: #fcfcfc;
	--_duration: 0.15s;

	display: inline flex;
	inline-size: calc(var(--_size-unit) * 2);
	block-size: var(--_size-unit);
	border: 2px solid transparent; /* 強制カラーモード対応のために`padding`ではなく`border`で余白をつくる */
	border-radius: calc(1px / 0);
	background-color: var(--_background);
	vertical-align: middle;
	appearance: unset;
	transition: background-color var(--_duration);

	&::before {
		content: '';
		flex: var(--_checked, 0);
		transition: flex var(--_duration) linear;
	}

	&::after {
		content: '';
		flex-shrink: 0;
		block-size: 100%;
		aspect-ratio: 1;
		border-radius: inherit;
		background: linear-gradient(var(--_foreground) 0 0), CanvasText; /* 強制カラーモードでも視認できる背景色として扱うようにする */
	}

	&:checked {
		--_checked: 1;
		--_background: oklch(60% 0.4 240deg);
	}
}

実装例ではflexで空の疑似要素を収縮させてスライドアニメーションを行っています。grid-template-columnsを使うとよりスマートに実装できますが、Safariでハンドル部分のblock-size:100%がうまく動かないのでこのようなアプローチをとっています。

見出しやリストのアイコンを中央寄せする場合に align-items:center を指定したほうが良いかは考えたほうがいい

見出しとかリストの横に図形やアイコンを配置するとき、`align-items: center`で上下中央揃えするかどうかは複数行になったことも考えて実装したほうがいい🫵 #Web制作 pic.twitter.com/PSK3qqlcLY

ポストを別枠で表示する

特にリストのアイコンに対してですが、アイコンをalign-items:centerで実装して複数行になった際に不格好になっているケースが散見されます。

行に対してセンタリングしたい場合はアイコンにmargin-block: calc((1lh - <アイコンのサイズ>) / 2)を指定すれば実現できるので、こちらの方法も検討すると良いでしょう。

また、個人的にはリストコンポーネントはspanやstrongが使用されるケースに備えてflexやgridは使わずにposition:absoluteやfloatでアイコンの位置調整をするケースが多いです。

リストマーカーに画像を使う実装

・::marker疑似要素はSafariで画像使えないから除外
・記述量をなるべく抑える
・position:absoluteなし
・li要素の子要素に条件をかけない(flexやgridだとテキストをラップしないとspanを含めたときに崩れる)

などを模索した結果選ばれたのはまさかのfloatだった pic.twitter.com/x9YwI1CvGH

ポストを別枠で表示する

floatを使用した実装例
li {
	--_icon-size: 1em;
	--_gap: 1em;
	--_icon-offset: calc(var(--_icon-size) + var(--_gap));

	display: block flow-root;
	padding-inline-start: var(--_icon-offset);

	&::before {
		content: '';
		float: inline-start; /* floatが指定された要素は`display:block`として扱われるので明示せずとも`margin-block`が使用できる */
		clip-path: var(--shape-star);
		block-size: var(--_icon-size);
		aspect-ratio: 1;
		margin-block: calc((1lh - var(--_icon-size)) / 2);
		margin-inline-start: calc(var(--_icon-offset) * -1); /* `float`した要素は`text-indent`で動かせない */
		background: linear-gradient(currentColor 0 0), CanvasText; /* 強制カラーモードでも視認できる背景色として扱うようにする */
	}
}

::marker疑似要素はSafariで画像が使用できなかったり位置調整がやりにくいなどのデメリットが目立つので使用するケースは少ないです。

1行を均等割り付けしたいなら text-align-last:justify を指定する

よくあるテーブルとかで見出しを均等割り付けする方法、昔は一文字ずつ`span`で囲って`justify-content: space-between`するという苦労が必要でしたが現在では`text-align-last: justify`を指定するだけで実現できます🫵 #Web制作 pic.twitter.com/1qI6zMGd0y

ポストを別枠で表示する

見出しを均等割り付けする方法はかつては一文字ずつspanで囲ってjustify-content:space-betweenするという苦労が必要でしたが、現在ではtext-align-last:justifyを指定するだけで1行を均等割り付けすることが可能です。

CSSの実装例
th {
	text-align-last: justify;
}

text-align-lastはブロックの最後の行もしくは強制的な改行の直前の行をどのように配置するかを設定するプロパティです。故に複数行になった場合は破綻してしまうのでその点は注意してください。

calc() 関数内でゼロ除算するとどうなるか?

算数におけるゼロ除算が今話題ですが、CSSでは`border-radius: calc(1px / 0)`とすると`1.67772e+07px`と計算されて絶対に破綻しない角丸が実装できます🫵 #Web制作 pic.twitter.com/mlohoC3W5A

ポストを別枠で表示する

算数のテストにおけるゼロ除算の解答に関して先月話題になっていましたが、CSSのcalc()関数内でゼロ除算を行うとinfinityとして扱われます。

「これはバグなんじゃないか?」とコメントを頂きましたが、これはバグではなく仕様(新しいタブで開く)であり、0以外の数値をゼロ除算すると標準の符号規則に従って +∞ または -∞ が生成されると仕様書には記されています。

Dividing a value by zero produces either +∞ or −∞, according to the standard sign rules.

CSS Values and Units Module Level 4 より引用

ゼロ除算の有効活用としては border-radius: calc(1px / 0) は border-radius: calc(infinity * 1px) と同じように扱われるため絶対に破綻しない角丸を実装できること、あとは z-index: calc(1 / 0) で上限値の2147483647と同等になるため同一レイヤー内であれば原則的に最前面に表示させることができるという活用法でしょう。

calc()関数内でinfinityを参照する方法と比較した時のメリットはゼロ除算のほうがタイプ数が少なくなる点、デメリットはCSSの仕様に精通していないとハックを疑われるという点でしょうか。

単色背景を親要素の幅を超えて画面いっぱいに表示したいなら border-image を使ったほうが良い

親要素の幅を超えて画面いっぱいに表示させる方法は`margin-inline: calc(50% - 50vi)`が有名ですが、画像のような単色背景であれば`border-image`で実装したほうがいい🫵

✅横スクロールを発生させない
✅コンテンツを親要素の幅に戻すための実装不要
✅ワンライナーで書ける

等の理由🫵 #Web制作 pic.twitter.com/bfUw9pQUBo

ポストを別枠で表示する

親要素の幅を超えて画面いっぱいに表示させる方法はmargin-inline: calc(50% - 50vi)が有名ですが、この方法は横スクロールを発生させたり、コンテンツを親要素の幅に戻す処理を行う必要があったりとデメリットも目立ちます。

単純に単色背景を親要素の幅を超えて画面いっぱいに表示させたいなら横スクロールに影響を及ぼさないborder-imageで実装するとベターでしょう。

CodePenで見る

CSSの実装例
section {
	--_background: #1c1c1c;

	border-image: linear-gradient(var(--_background) 0 0) fill 0 / /0 100lvi;
}

border-imageで単色背景を指定する場合はlinear-gradient()などのグラデーション関数を使用します。最短の記述でlinear-gradient(<color> 0 0)と指定すると単色になります。

Sassを使用している方はコメント扱いされないように連続するスラッシュの間に半角スペースを含めておきましょう。

ホバー時の opacity の指定は避けたほうがいい

ホバーしたときに背景色を透過する実装、考えなしに`opacity`を指定してしまうと文字含めて全てが透過してしまう。画像のような方法で背景色だけ透過させることを推奨します🫵

これは背景色を明るくするor暗くするのにも流用可能。というか透過するよりもそっちのほうが絶対良い🫵 #Web制作 pic.twitter.com/ijl9IeXx3N

ポストを別枠で表示する

ホバーしたときに背景色を透過する実装はよく見かけますが、考えなしにopacityを指定してしまうと文字含めて全てが透過してしまいます。もしもホバー時に背景色を透過することが望まれる場合はcolor-mix()関数などを使用して背景色だけ透過することを推奨します。

🙅‍♂ Not Recommended
.button {
	background-color: var(--_background);

	&:hover {
		@media (any-hover: hover) {
			opacity: 0.8;
		}
	}
}
🙆‍♂ Recommended
.button {
	background-color: color-mix(in sRGB, var(--_background) var(--_opacity, 100%), transparent);

	&:hover {
		@media (any-hover: hover) {
			--_opacity: 80%;
		}
	}
}

ただし、ホバー時に透過するという実装自体が「意識させたいはずのものを目立たなくさせる」という本末転倒感あるアプローチのため、なるべくなら代替案を考えたほうが良いかもしれません。color-mix()関数を使用したこの実装は色を暗くするor明るくするといったアプローチにも流用可能なため、そちらも考慮するとよいでしょう。

色を暗くする
.button {
	background-color: color-mix(in sRGB, var(--_background), #000 var(--_darken, 0%));

	&:hover {
		@media (any-hover: hover) {
			--_darken: 20%;
		}
	}
}
色を明るくする
.button {
	background-color: color-mix(in sRGB, var(--_background), #fff var(--_lighten, 0%));

	&:hover {
		@media (any-hover: hover) {
			--_lighten: 20%;
		}
	}
}

なお、昨日リリースされたFirefox 128より相対カラー構文(新しいタブで開く)が全モダンブラウザでサポートされたので、こちらの使用も検討してみても良いでしょう。

Using relative colors - CSS: Cascading Style Sheets | MDN

The CSS colors module defines relative color syntax, which allows a CSS <color> value to be defined relative to another color. This is a powerful feat…

developer.mozilla.org

Scroll-driven Animations で途中横スクロールするセクションを実装する

動画のような途中でセクションを横スクロールする実装、現代ではJSを書かずともこれだけのCSSの記述で実現できます🫵

「でもSafariとFirefoxで使えないんでしょ?」とお思いのそこのアナタ!次のJSを読み込めばそれらでも動きます🫵 https://t.co/g8eVvdwrj7

実装例 https://t.co/cQMGVLyb21 pic.twitter.com/Oa0Cq4rk9z

ポストを別枠で表示する

ギャラリーサイトでよく見かける途中で横スクロールするセクションの実装、GSAPのScrollTriggerで実装されるケースが殆どですが、現代ではScroll-driven Animationsを使用すればCSSのみで実装できます。Scroll-driven AnimationsはまたしてもChromeおよびEdgeのみのサポートですがScroll-timeline Polyfill(新しいタブで開く)を導入すれば非サポートのSafariやFirefoxでも動作します。

途中横スクロールするセクションの実装例
CSSの実装例
.horizontal {
	--_scroll-amount: 500;

	overflow: clip;
	min-block-size: calc(var(--_scroll-amount) * 1dvb);
	view-timeline-name: --horizontal-section;
}

.container {
	position: sticky;
	inset-block-start: 0;
	inline-size: calc(var(--_scroll-amount) / 2 * 1dvmax);
	min-block-size: 100dvb;
	animation: linear horizontal-scroll both;
	animation-timeline: --horizontal-section;
	animation-range: contain 0% contain 100%;
}

@keyframes horizontal-scroll {
	to {
		translate: calc(-100% + 100dvi);
	}
}

難点としては横スクロールする要素に固定値の幅を指定しないといけないということ。故に内容物に合わせてスクロール量を決定するのが難しいため、後述するパララックスとは違いScrollTriggerで実装したほうがベターな気がします。

Scroll-driven Animations でパララックスを実装する

現代のパララックス、アニメーション部分はたったこれだけのCSSの記述で実装できる🫵

昨日の横スクロールセクションの実装例と同様にscroll-timelineのPolyfillを導入すればSafariやFirefoxでも動作するから試しなさい🫵 https://t.co/N8fNn8udsp

実装例🫵 https://t.co/ywlngN9yOX #Web制作 pic.twitter.com/veekyzUIz4

ポストを別枠で表示する

こちらもScroll-driven Animationsを使用したアニメーションの紹介。パララックスに関しては非常に少ない記述量で実現できます。こちらもPolyfillに対応。

パララックスの実装例
CSSの実装例
.parallax {
	--_translate: 200px;

	animation: parallax linear both;
	animation-timeline: scroll(root);
}

@keyframes parallax {
	from {
		translate: 0 calc(var(--_translate) * -1);
	}

	to {
		translate: 0 var(--_translate);
	}
}

記述量の少なさ、アニメーションをCSSで完結できる、JSを使用した実装と比較してコストがかなり低いなどといった理由から、ゴリゴリのアニメーションではなく部分的にパララックスを導入したい場合はPolyfillをアニメーションライブラリのように扱ってScroll-driven Animationで実装したほうがよいかもしれません。

Scroll-driven Animations と @property ルールでページのスクロール量をカウントアップする表示を実装する

ページのスクロール量をカウントアップする表示、現代ではCSSのみで実装できます🫵

※Scroll-driven Animationsと@\property構文が両方サポートされているChromeとEdgeのみ

supports機能クエリを使って有効な環境でのみ表示させるといいと思います🫵

サンプル: https://t.co/Lpjc4ooP5m #Web制作 pic.twitter.com/r2SkTx0BFz

ポストを別枠で表示する

当ブログの左下にスクロールインジケーターを導入しましたが、こちらもScroll-driven Animationsを使用して実装しています。また、カウントアップに関しては@propertyルールでカスタムプロパティを定義することで実現しています。

HTMLの実装例
<div class="progress">
	<svg viewBox="0 0 100 100">
		<circle cx="50" cy="50" r="40"></circle>
	</svg>
</div>
CSSの実装例
/* プログレスサークルの親要素 */
.progress {
	display: block grid;
	grid-template-areas: 'stack';
	place-items: center;
	inline-size: 200px;
	aspect-ratio: 1;
	counter-set: increment-counter var(--_increment);
	font-variant-numeric: tabular-nums;
	animation: increment-counter linear;
	animation-timeline: scroll(root);

	@supports not (animation-timeline: scroll(root)) {
		display: none;
	}

	&::after,
	& > * {
		grid-area: stack;
	}

	&::after {
		content: counter(increment-counter) '%';
	}

	& svg {
		--_diameter: 80; /* 直径 */
		--_circumference: calc(var(--_diameter) * 3.14); /* 円周 */
		--_dashoffset: calc(var(--_circumference) - (var(--_increment) / 100) * var(--_circumference));

		inline-size: 100%;
		block-size: 100%;
		fill: none;
		stroke: currentColor;
		stroke-dasharray: var(--_circumference);
		stroke-dashoffset: var(--_dashoffset);
		stroke-width: 5;
		rotate: -90deg;
	}
}

/* @propertyでCSS変数を定義することでテキストのカウントアップが可能に */
@property --_increment {
	syntax: '<integer>';
	inherits: true;
	initial-value: 0;
}

@keyframes increment-counter {
	to {
		--_increment: 100;
	}
}

@propertyルールは昨日リリースされたFirefox 128により全モダンブラウザでサポートされました(iOSは16.4よりサポート)。CSS変数に型定義ができるだけでなく、background-imageやborder-imageといった本来アニメーションできなかったプロパティもアニメーションできるようになったり、@keyframes内でカスタムプロパティの定義を行えるようになったりとアニメーションの幅が広がったと言えます。

@propertyを使用したアニメーションの実装例

しかし、いくつかバグが存在するようで、このスクロールインジケーターにおいてもSafariだとカウントアップが動かず、Firefoxではstroke-dashoffsetのアニメーションがうまくいかないことを観測しているのでScroll-driven Animations非対応のブラウザでは@supports機能クエリで非表示にしています。


これらの投稿が良かったという方はXのフォローをよろしくおねがいします!