Defensive CSSを書こう
はじめ
はじめまして、チームラボ フロントエンド班のLIUです。中国四川出身で、CSSとホラー映画が大好きマンです。新卒でチームラボに入社し、現在フロントエンドエンジニアとして働いています。
この記事では、フロントエンド勉強会(チームラボ内で、隔週で開催しているフロントエンドに関するLT会)で発表したDefensive CSSについての内容を紹介します。
レスポンシブデザイン
タイトルは「Defensive CSS」ですが、まずはレスポンシブデザインについて話したいと思います。 モバイル通信の発展や、スマホブラウザがPC並みのパフォーマンスを実現したことで、レスポンシブデザインが広く普及しました。これにより、フロントエンジニアはスマホ専用サイトの制作から脱却し、CSSを用いて簡単に画面サイズに応じたレイアウトを実装できるようになりました。 しかし、レスポンシブデザインにはメリットだけでなく、以下のような原因で表示が崩れる問題もしばしば見受けられます。
- 画面サイズが確定しないこと
- SP/PCや、画面サイズがLarge/Smallに変わる際のレイアウトの大幅変更
- デバイスやブラウザの独自挙動(例:iOSのアドレスバーの伸縮)
- 極端な状況(例:極めて狭い画面幅)
- 実際の使用状況がエンジニアの予想外であること
これらの問題は、主にmedia queryで対処されますが、それには限界があります。
防御的なCSSが対策
そこで防御的なCSS、すなわちDefensive CSSの実践が重要です。
Defensive CSSは、2021年にAhmad Shadeed氏によって提案された用語で、動的なコンテンツや予期せぬユーザー行動によって生じるレイアウトの問題への一連の対策を指します。Shadeed氏の記事では、特定のCSS問題の解決策が列挙されており、レスポンシブデザインの表示問題はほとんど解決できるとされています。
ただし、Shadeed氏が紹介するのは、問題が発生した後の方法論に過ぎず、実運用ではバグチケットが発行された際の対処法となります。個人的には、マークアップ段階で防御的なCSSを実装するマインドセットを持ち、予測可能なスタイル崩れを予防することが重要だと考えています。
CSSの書き方をモダンにしましょう
モダンなreset CSSを使いましょう
しかし、デフォルトの表示が予期せぬものである場合、一つずつ修正するよりも、Reset CSSを使って一括でスタイルをリセットする方が、開発やデバッグの際に便利です。特に、デフォルトのline-height
やlist-style
などはReset CSSでリセットすることをお勧めします。
完璧なReset CSSを最初から用意する必要はありません。実際のデザインや実装の問題に応じて、適宜見直しや追加を行うことが重要です。
モダンなCSSでレイアウトを作成しましょう
grid
を、1次元レイアウトにはflex
を使用するという有効なアプローチが提案されています。
実際、grid
とflex
はレイアウト実装において議論されることが多いモダンCSSメソッドですが、使い分けの明確なルールやベストプラクティスはまだ確立されていません。Shadeedのアプローチでは、複雑な2DレイアウトにはGridが適しており、シンプルな1DレイアウトにはFlexboxが最適です。Gridはより大規模な全体のレイアウトに、Flexはその中の個別コンポーネントに適しています。この方法を採用することで、レイアウトの柔軟性が向上し、崩れるリスクも低減されるでしょう。
論理的プロパティを使いましょう
従来の物理的プロパティと異なり、書字方向に依存する論理的プロパティを適切に使うためには、ブロック方向(テキストの流れに垂直な方向)とインライン方向(テキストの流れに平行な方向)の概念を理解することが重要です。
Modern CSS in Real Lifeより引用
これらの方向性に基づいて、従来使われてきた寸法、スペーシング、境界、位置指定などのプロパティは、ほとんど論理的プロパティに置き換え可能です。
物理的プロパティ | 論理的プロパティ |
---|---|
width | inline-size |
right | inset- inline-end |
margin-left | margin-inline-start |
overflow-x | overflow- inline |
resize: horizontal | resize: inline |
text-align: right | text-align: end |
この記事では、論理的プロパティの利点が特に多言語対応の場面で明らかにされています。これまでは物理的プロパティを用いると、異なる書字方向を持つ言語間でのレイアウト変更が困難でしたが、論理的プロパティを使うことで、追加の実装なしにこれを実現できるようになります。これは、工数削減とユーザビリティ向上に寄与します。さらに、記事では以下のような視点が示されています。
多言語対応がビジネス要件に含まれていない場合でも、サイトを自動翻訳するユーザーが存在するために実装が重要である
Modern CSS in Real Lifeより引用
という考え方です。これは、少しでも外国語ユーザーを想定した時、論理的プロパティの採用を考慮すべきというフロントエンジニアのマインドセットに関連しています。
防御的CSS
動的なコンテンツによる表示崩れを一般的に述べると、以下のような問題が挙げられます。
- 内容がはみ出すこと
- 画像やレイアウトが歪むこと
- 要素が重なること
- 突然のレイアウトのずれ
- など
Shadeed氏は、表示崩れへの対策として、彼の記事とDefensive CSSのウェブサイトでさまざまな解決策を分類して提供しています。
具体的な対策については、読者の皆さんにAhmad Shadeedの記事Defensive CSSやDefensive CSSのウェブサイトを直接ご覧いただくことをお勧めします。今回はその中で、私や私のチームメンバーが以前は気づいていなかった、重要なポイントのいくつかを挙げます。
- ベンダーセレクタの使用
特にshadow DOMを操作する必要がある複雑なケースでは、異なるブラウザ向けのセレクタを組み合わせて使うことは避けるべきです。サポートされていないセレクタがあると、全体が無効になります。
// bad
input::-webkit-input-placeholder,
input:-moz-placeholder {
color: #222;
}
そのため、セレクタは個別に書くことが推奨されます。
// good
input::-webkit-input-placeholder {
color: #222;
}
input:-moz-placeholder {
color: #222;
}
-
画面高さによった表示崩れ
画面の幅に関する議論は頻繁に行われますが、高さに基づく表示の制御についてはあまり話されません。特にposition: sticky;
を使用している場合など、高さによる表示崩れのリスクも考慮すべきです。 -
スクロール連鎖
モーダルやダイアログの存在時、下層ページがスクロールする「スクロール連鎖」現象は、マークアップ時には見落とされがちですが、実際の運用ではよく遭遇する問題です。
これを防ぐためには、overscroll-behavior
プロパティを適切に設定することが有効です。これにより、モーダルやダイアログのスクロール時に下層のページがスクロールするのを抑制できます。
.scrollable-content {
overscroll-behavior-y: contain;
overflow-y: auto;
}
- 画像の歪み
画像の表示比率が固定の際にaspect-ratio
を使用すると、画像の歪みは防げますが、レスポンシブデザインにおいては予期せぬ余白が生じることがあります。この問題を解決するためには、aspect-ratio
の代わりにobject-fit: cover;
を用いることが効果的です。これにより、画像の比率を保ちつつ、コンテナに合わせて適切に調整されます。
.image-container {
object-fit: cover;
}
figmaなどのデザインツールを参照しながらCSSを実装する際には、以下の点に特に注意することが重要です。
- 動的コンテンツを含むコンテナのoverflow時の挙動をチェックする。
-
position: static;
以外の要素の様々な状況での表示を常に確認する。 - 画像(
img
タグやbackground-image
で設定されたもの)の表示状態を常にチェックする。 - コンテナのサイズは、
max-
やmin-
プロパティを使って実際の状況に応じて指定する。 - コンテンツが長い場合などでも、適切な余白が保たれているかを確認する。
最後に
CSSに起因する表示問題の多くには対策がありますが、実装時には仕様を最優先で考慮することが重要です。例えば、何かしらのコンテナで実装していて文字がはみ出す問題に遭遇した場合、単にtext-overflow: ellipsis;
などを追加するのではなく、まずデザイナーや仕様決定者に相談し、文言がはみ出る際にレイアウトとコンテンツのどちらを優先するかを決めるべきです。このようなアプローチは、実装の効果的な方向性を定め、問題の根本的な解決につながります。
最後までお読みいただきありがとうございました!
Discussion