レスポンシブ
アプリを多様な画面幅へ適応させるための前提知識と規約を定める
本チームのアプリは、モバイル・タブレット・PC の 3 つの画面サイズに対応する。1 つの実装をこれらの幅すべてで破綻なく見せるための前提知識と、本チームで守る規約をここで正本として定める。
前提知識
規約を述べる前に、レスポンシブを支える基本概念を最小限おさえる。
レスポンシブの定義
レスポンシブとは、1 つの HTML を、スマートフォンから PC までの多様な画面幅に CSS で適応させる手法である。画面幅ごとに別ページを作るのではなく、同じ内容を画面幅に応じて配置を組み替える。これにより、どの端末でも同じ情報へ等しく到達できる。
ビューポートと CSS ピクセル
ビューポートとは、ブラウザが Web ページを描画する表示領域である。スマートフォンの物理的な画素数は多いが、レイアウトの基準には CSS ピクセルという論理単位を使い、複数の物理画素を 1 CSS ピクセルにまとめて表示する。したがって、レイアウトは物理解像度ではなく CSS ピクセル幅で考える。
Next.js が既定で適切なビューポート設定を出力するため、個別の設定は原則不要である。後述のとおり、ズームを禁止する設定だけは加えない。
レスポンシブの 3 本柱
レスポンシブは、次の 3 つの組み合わせで成り立つ。
- 流動グリッド — 幅を固定値ではなく割合や比率(
%・fr・flex)で持たせ、領域に応じて伸縮させる。 - 可変メディア — 画像や動画が親要素を超えないようにし、領域に収める。
- ブレークポイント — ある幅を境に、レイアウトそのものを切り替える。
流動グリッドと可変メディアで大半を吸収し、それでも破綻する箇所だけブレークポイントで切り替える、という重ね方が基本である。
モバイルファースト
モバイルファーストとは、最小幅の見た目を基準(base)として書き、画面が広がるにつれて min-width で上書きしていく考え方である。狭い画面は要素が縦並びの単純な構造になりやすいため、その単純な状態を基準として書き、広い画面で段組みを足すほうが、例外の少ない素直な CSS になる。
コンテンツ基準の分岐
ブレークポイントは、特定の端末の幅を狙って決めない。端末の種類と画面幅は年々増え続けるため、機種を追いかけるときりがないからである。代わりに、レイアウトが破綻する幅を境にする。文字が詰まりすぎる、要素が潰れるといった、コンテンツ側の都合で切替点を決める。
メディアクエリとコンテナクエリ
幅に応答する方法は 2 つある。
- メディアクエリ — ビューポート(画面)の幅に応答する。ページ全体のレイアウトに使う。
- コンテナクエリ — 親要素の幅に応答する。コンポーネント内部のレイアウトに使う。
同じカードでも、広いメイン領域に置かれるか狭いサイドバーに置かれるかで適切な見た目は変わる。コンテナクエリは置かれた場所の幅に応答するため、部品を文脈から独立させられる。使い分けは後述する。
設計方針
レスポンシブは、次の方針に従って実装する。
流動優先とブレークポイントの最小化
まず流動グリッドと可変メディアで、どの幅でも破綻しないように作る。そのうえで、段組みが変わるなどどうしても必要な箇所だけ、ブレークポイントを足す。切替点を増やすほど確認すべき状態が増えるため、ブレークポイントは最小限にとどめる。
モバイルファーストでの記述
base に最小画面の見た目を書き、md: や lg: の min-width で上書きする。max-width 起点で広い画面から狭めていく書き方はしない。
可変の主役はレイアウト部品
Container・Grid・Stack などのレイアウト部品が幅に応じて変化し、末端のコンポーネント(ボタンやカードなど)は親のレイアウトに追従する。末端が自前で大きく作り替えない。これにより、幅への応答という関心をレイアウト部品へ集約する。
ブレークポイントの規約
- 標準のブレークポイントに揃える。モバイルを base(最小幅)とし、タブレットは
md(768px 以上)、PC はlg(1024px 以上)を主たる切替点とする。全コンポーネントでこの基準に揃え、部品ごとに切替点がずれないようにする。カードグリッドの細かな段階など、必要なときに限りsm(640px)やxl(1280px)を補助的に使う。 - 独自のブレークポイントを定義しない。上記は Tailwind の既定値である。独自の切替点を増やすと基準が分散するため、既定のスケールをそのまま使う。
- 任意値でフルイドに作らない。文字サイズや余白を
clamp()や任意値で連続的に変化させる手法はとらない。任意値はトークンのスケールから外れるためである。連続的な変化が欲しい場面でも、text-*などの段階をブレークポイントで切り替えて表現する。
// ✓ モバイルファースト。base に最小幅、md/lg で上書きする
<h1 className="text-2xl md:text-3xl lg:text-4xl">所蔵資料</h1>
// ✗ 任意値で連続変化させる(スケールから外れる)
<h1 className="text-[clamp(1.5rem,4vw,2.25rem)]">所蔵資料</h1>コンポーネントの責務
レスポンシブは、各コンポーネントの責務とする。汎用 UI コンポーネントは className を公開しないため、利用側はレスポンシブのクラスを足せない。画面幅で見た目が変わる部品は、レスポンシブ対応の props(応答的なバリアント)かコンポーネント内への焼き込みで表現する。利用側にクラスを渡させない。
部品内部のレイアウトには、メディアクエリではなくコンテナクエリを優先する。コンテナクエリは部品が置かれたコンテナの幅に応答するため、どの文脈に置かれても自身の幅だけで正しく組み替わる。これは、部品を文脈から独立させるという設計方針と一致する。ページ全体の骨格はメディアクエリで、部品内部のミクロなレイアウトはコンテナクエリで、と使い分ける。
// ✓ 親をコンテナにし、子はコンテナ幅で組み替える
<div className="@container">
<article className="flex flex-col @md:flex-row">{/* ... */}</article>
</div>横スクロールとメディア
- 横スクロールを生まない。固定幅の要素、はみ出す子要素、折り返さない長い文字列は、狭い画面で横スクロールを生む。
min-w-0・flex-wrap・break-wordsなどで、320px 幅でもページ全体に横スクロールが出ないようにする。 - メディアを領域に収める。画像・動画・埋め込みは親要素を超えないようにし、
aspect-ratioで領域を先に確保してレイアウトのずれを防ぐ。画像はnext/imageを用いる。 - 2次元コンテンツは例外とする。データテーブルや地図など、本質的に2次元で縮約できないものは、無理に 1 カラムへ畳まない。その要素自体を横スクロールできる領域に閉じ込め、ページ全体の横スクロールは避ける。
アクセシビリティ
レスポンシブは、それ自体がアクセシビリティの要件と重なる。
- 320px 幅で横スクロールを生まない。320px 幅でも、横スクロールせず内容を読める状態を保つ(WCAG 1.4.10)。データテーブルなど2次元のものは前述のとおり例外とする。
- 400% ズームに耐える。文字を 400% まで拡大しても、横スクロールせず読める状態を保つ。
- ズームを妨げない。ビューポート設定で
user-scalable=noやmaximum-scaleを指定せず、利用者のズームを禁止しない。 - 十分なヒット領域を確保する。操作対象は、指で狙いやすい大きさにする。具体的な基準はアフォーダンスに従う。
確認手段
実装後は各アプリの開発用ページ(museum なら /dev/components)と実画面で、次を確認する。
- モバイル・タブレット・PC の各幅で、表示と操作が破綻しないこと。
- 320px 幅で、ページ全体に横スクロールが出ないこと。
- 400% ズームで、横スクロールせずに読めること。
関連項目
- アフォーダンス — ヒット領域・可視フォーカスなどの操作の手がかり