はじめに
2025年は CSS の標準仕様が続々と実用化し、実務レベルで活用できる機能が増えています。本記事では特に開発現場で役立つ新機能を紹介します。第1回では以下の機能を取り上げます。
- コンテナクエリ(Container Queries)
- スクロールドリブンアニメーション (Scroll-driven Animations)
- Subgrid
- text-wrap: balance、text-wrap: pretty
CSS の紹介
コンテナクエリ(Container Queries)
メディアクエリがビューポートサイズに基づくのに対し、コンテナクエリは親要素のサイズに基づいてスタイルを変更できる新機能です。コンポーネントが配置される場所に関係なく、そのコンテナサイズに応じて最適なスタイルを適用できます。
実装手順
1. 基準となるコンテナコンテキストを作成する
container-type
プロパティで特定要素を「コンテナ」に指定します。どの軸(幅 / 高さなど)をクエリ対象にするかを宣言する役割を持ちます。
値 | 説明 |
---|---|
inline-size | 横幅(インライン軸)のみを監視するコンテナクエリ用コンテナを作成 |
scroll-state | スクロール状態(スクロール位置、スナップ状態など)を監視するコンテナを作成 |
size | 横幅と高さ(両軸)を監視するコンテナクエリ用コンテナを作成 |
ここではよく使われる inline-size
を例にします。
.card { container-type: inline-size; }
2. クエリを作成する
@container
でクエリを記述します。ブラウザは最も近い祖先コンテナを探索し、条件が一致した場合スタイルを適用します。
@containerの指定方法
/* 幅の条件 */ @container (min-width: 400px) { /* 400px以上 */ } @container (max-width: 600px) { /* 600px以下 */ } @container (width > 300px) { /* 300pxより大きい */ } /* 高さの条件 */ @container (min-height: 200px) { /* 200px以上 */ } @container (max-height: 400px) { /* 400px以下 */ } /* 複数条件 */ @container (min-width: 300px) and (max-width: 800px) { /* 300px〜800px */ } @container (min-width: 400px) or (min-height: 300px) { /* いずれかを満たす */ }
実用例
/* カードコンポーネント */ .card { container-type: inline-size; border: 1px solid #ddd; padding: 1rem; } .card-header { font-size: 1rem; font-weight: bold; } .card-content { display: block; } @container (min-width: 300px) { .card-header { font-size: 1.2rem; } .card-content { display: flex; gap: 1rem; } } @container (min-width: 500px) { .card-header { font-size: 1.5rem; } .card-content { gap: 2rem; } }
このカードコンポーネントはコンテナ幅に応じてレイアウトが変化します。300px 未満では縦並びになり、300px 以上で横並びに、500px 以上でフォントサイズと余白が拡張されます。
スクロールドリブンアニメーション(Scroll-driven Animations)
JavaScript を使わず、スクロール位置に連動したアニメーションを CSS だけで実現できます。プログレスバーやフェードインなど基本的な演出を軽量に追加できます。
実装手順
1. animation-timeline を指定する
スクロール位置や要素の可視範囲をタイムラインとして利用します。scroll()
はスクロール位置を、view()
は要素のビューポート内での可視割合を時間軸にマッピングします。
animation-timeline
の設定
値 | 説明 |
---|---|
scroll() | スクロールコンテナのスクロール位置(全体のスクロール量)に基づいて進行。要素の可視性は無関係。 |
view() | アニメーション対象の要素がスクロールコンテナー内でどのように表示されているか(見え始め、見え終わり、表示割合)に基づいて進行します。 |
/* スクロール位置に応じたアニメーション */ .element { animation-timeline: scroll(); } /* 特定要素が画面内に入った時のアニメーション */ .element { animation-timeline: view(); }
2. 基本的なアニメーションを定義してタイムラインに同期させる
@keyframes
で通常どおりアニメーションを定義し、animation-duration: auto;
を指定することでスクロール進行と同期させます。
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .scroll-item { animation: fadeIn linear; /* タイムライン進行に合わせるため duration は auto にする */ animation-duration: auto; animation-timeline: view(); /* 可視範囲の「入り始め」→「出終わり」までを進行区間に */ animation-range: entry 0% exit 100%; }
3. animation-range で進行区間を調整する
タイムラインのどの範囲をアニメーションの 0%〜100% に対応させるかを柔軟に指定できます。
/* 要素が画面に入ってから出るまで */ animation-range: entry 0% exit 100%; /* 要素が完全に画面に入った時のみ */ animation-range: contain 0% contain 100%; /* スクロール開始から50%の位置まで */ animation-range: 0% 50%; /* 要素が画面中央に来た時 */ animation-range: entry 50% exit 50%;
実用例
/* プログレスバー */ @keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } .progress-bar { transform-origin: left; animation: progress linear; animation-duration: auto; animation-timeline: scroll(root); } /* 要素のフェードイン */ .fade-in-item { opacity: 0; animation: fadeIn linear; animation-duration: auto; animation-timeline: view(); animation-range: entry 0% exit 100%; }
プログレスバーの例は scroll(root)
によってページ全体のスクロール量に応じて横幅が伸びます。読書進捗やページ進行状況の可視化に向いています。
フェードインの例は view()
によって要素が画面に入るタイミングで開始します。animation-range: entry 0% exit 100%
で見え始めから見え終わりまでスクロールと同期して進行します。
/* アニメーション軽減設定ユーザー向け無効化 */ @media (prefers-reduced-motion: reduce) { .scroll-item, .fade-in-item, .progress-bar { animation: none; } }
ユーザー設定に応じてアニメーションを抑制し、アクセシビリティを確保します。
Subgrid
Subgrid は CSS Grid の入れ子で「子要素が親と同じ列や行のトラックを再利用できる」機能です。これが無い場合、カード一覧などで各カード内部レイアウトが微妙にずれ、見出し・テキスト・ボタンの縦位置が揃いにくくなります。
Subgrid を使うと「親が 3 列なら子も同じ 3 列幅」を共有できます。列定義を親 1 箇所で変更するだけで入れ子レイアウト全体をまとめて更新 できます。効果は次のとおりです。
- 見た目の一貫性を維持できる
- 重複したメディアクエリやトラック定義を削減できる
- 後から列幅を調整する保守コストを低減できる
というメリットがあります。
実装手順
1. 親グリッドでレイアウトトラックを定義する
まず親コンテナで列(列数・比率)や行を通常どおり設定します。ここで定義したトラックが子で再利用されます。
.parent-grid { display: grid; grid-template-columns: 1fr 2fr 1fr; grid-template-rows: auto auto; gap: 20px; }
プロパティ解説
- display: grid;
- 要素をグリッドコンテナ化し、子要素をグリッドアイテムとして扱えるようにする。
- grid-template-columns: 1fr 2fr 1fr;
- 3 列を 1:2:1 の比率で分配する。中央を広めに確保した構成である。
- grid-template-rows: auto auto;
- 先頭 2 行は内容量に応じて自動で高さが決まる。追加行は暗黙トラックとして生成される。
- gap: 20px;
- 行間・列間の両方を 20px に設定する。外周余白は
padding
で調整する。
- 行間・列間の両方を 20px に設定する。外周余白は
補足: 後続の Subgrid 例で子がこの列トラック (1fr 2fr 1fr) を継承する前提になります。列定義を親 1 箇所で持つことで、全カードの整列と保守性を高められます。
2. 子要素をグリッド化し subgrid を指定する
子グリッド側で display: grid;
を指定します。続いて grid-template-columns: subgrid;
/ grid-template-rows: subgrid;
を書くと親で定義されたトラック情報を継承します。適用する軸は列だけ、行だけといった最小構成にもできます。
.child-subgrid { display: grid; grid-column: 1 / -1; /* 親グリッド全幅を占有 */ grid-template-columns: subgrid; /* 親の列トラックを継承 */ grid-template-rows: subgrid; /* 親の行トラックを継承 */ }
プロパティ解説(child-subgrid)
- display: grid;
- 子要素を内部グリッドとして再配置可能にする。
- grid-column: 1 / -1;
- 親グリッドの最初のラインから最後のラインまでを跨いで全幅を占有する。
- grid-template-columns: subgrid;
- 親で定義した列トラック幅と列ライン名を継承する(この軸では新しいサイズは追加できない)。
- grid-template-rows: subgrid;
- 親の行トラック構造を継承する。行高と gap(その軸の間隔)も親の設定に従う。
実用例
.card-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .card { display: grid; grid-template-columns: subgrid; grid-template-rows: auto 1fr auto; border: 1px solid #ccc; padding: 1rem; } .card-header { grid-column: 1 / -1; } .card-content { grid-column: 1 / -1; } .card-footer { grid-column: 1 / -1; }
この例ではカードが親グリッドの 3 列構造を継承します。各カード内部の要素が親の列ラインに沿って整列します。従来の Grid ではカードごとに独立定義が必要でしたが、Subgrid により統一感と保守性が向上します。
赤線は揃い方の違いを見るための参考線です。Subgrid ありでは要素の横位置が揃い、無しではカードごとにずれます。
text-wrap: balance、text-wrap: pretty
テキストの折り返しをブラウザに最適化させる機能です。従来は手動で改行調整や文字数制限をしていた行ムラや一文字行を軽減し、見出しと本文の可読性を底上げします。
実装手順
1. 基本の値を理解する
用途に応じて以下 2 つを使い分けます。
値 | 目的 / 動作 | 想定用途 |
---|---|---|
balance | 行長バランス最適化(最終行だけ極端に短くなる崩れを緩和) | 見出し / キャッチコピー / カードタイトル |
pretty | 不自然な改行回避(単語途中・一文字行を抑え読みリズム維持) | 本文 / 長め説明 / 多言語混在 |
/* バランスの取れた折り返し(見出し向け) */ .title { text-wrap: balance; } /* 美しい折り返し(本文向け) */ .content { text-wrap: pretty; }
balance
は短いテキストで最終行だけ極端に短くなる状況を緩和し、pretty
は極端な分割や一文字行を避けて自然な改行位置を選びます。
2. 見出しと本文に適用する
見出し(短文)は視覚的な均衡を優先し、本文(長文)は可読性とリズムを優先します。
/* 見出しでバランス調整 */ h1, h2, h3 { text-wrap: balance; max-inline-size: 500px; } /* 本文で美しい改行 */ p { text-wrap: pretty; max-inline-size: 650px; /* 読みやすい行幅に制限 */ }
3. 行幅を最適化する
max-inline-size
などで行長を調整します。目安: 見出し 15〜25ch / 本文 55〜70ch。75ch 超なら制限し、30ch 未満なら幅や余白を見直します。
4. コンポーネント単位で調整する
カードや見出しなど密度や強調度が異なる要素は別々に調整します。
実用例
.article-title { font-size: 2rem; text-wrap: balance; max-inline-size: 200px; /* 20文字程度で制限 */ } .article-content { text-wrap: pretty; max-inline-size: 650px; line-height: 1.6; } .card-title { text-wrap: balance; max-inline-size: 150px; }
text-wrap: balance
は見出しなど短いテキストで最終行だけ 1〜2 文字になる崩れを低減し、視線リズムを整えます。text-wrap: pretty
は本文で一文字行や不自然な改行・字間空きを避け、読みやすい改行位置を選びます。
どちらも max-inline-size
(またはコンテナ幅制御)と併用すると効果が安定します。調整なしではブラウザ側で十分に行を分散できず、改善が限定的になる場合があります。
まとめ
最近登場した便利なCSSの機能を使えば、Webサイトのデザインが美しくなるだけでなく、コードの管理もずっと楽になります。
今回紹介した新機能とそのメリットは、
- コンテナクエリ & サブグリッド: デザインのパーツ(コンポーネント)を、ページのどこに置いてもレイアウトが崩れず、再利用しやすくなる
- スクロール連動アニメーション: JavaScriptを使わずに、スクロールに合わせて動くアニメーションを実装できる。これにより、ユーザーにとって心地よい視覚的なフィードバックを手軽に加えられる。
- text-wrap: 見出しや文章が変なところで改行されるのを防ぎ、テキストを読みやすく自動で調整してくれる。
まずは小さな部品から試してみて、どれだけコードがスッキリし、読みやすくなるかを確認しながら、少しずつサイト全体に適用していくのがおすすめです。 次回も、開発現場で役立つCSSの新機能をご紹介しますので、お楽しみに!
暇があったらクライミングしているフロントエンドエンジニアです。