余白の調整に clamp()
で最小値~最大値で可変にする実装が、最近ではよく使われますよね。
レスポンシブ対応として非常に便利なのですが、アクセシビリティの拡大表示(ズーム)を考慮すると、ちょっとした落とし穴があるのです。
よくある clamp() の指定例
例えば、余白を clamp()
で調整する場合は次のように書くことが多いでしょう。
:root {
--clamp-base: 16;
--clamp-viewport-min: 375;
--clamp-viewport-max: 1440;
--spacing-lg-min: 24;
--spacing-lg-max: 48;
--spacing-lg-slope: calc(
(var(--spacing-lg-max) - var(--spacing-lg-min)) / (var(--clamp-viewport-max) - var(--clamp-viewport-min))
);
--spacing-lg-intersection: calc(
(var(--spacing-lg-max) - var(--clamp-viewport-max) * var(--spacing-lg-slope)) / var(--clamp-base)
);
--spacing-lg: clamp(
calc(var(--spacing-lg-min) / var(--clamp-base) * 1rem),
calc(var(--spacing-lg-intersection) * 1rem + 100 * var(--spacing-lg-slope) * 1vw),
calc(var(--spacing-lg-max) / var(--clamp-base) * 1rem)
);
}
詳細の説明は省きますが、
- スマートフォンでは
--spacing-lg-min
のサイズ - PCでは
--spacing-lg-max
のサイズ - その間は可変でスムーズに伸縮
となり、とても使い勝手の良いパターンですよね。
アクセシビリティでの落とし穴って?
問題は、WCAG 2.0 達成基準 1.4.4「テキストのサイズ変更」 に対応しようとする時です。
この基準はもともとスマートフォンが一般化する前に「PCで200%まで拡大しても閲覧できるように」という意図で策定されましたが、スマートフォンのブラウザにも同じ条件が適用されます。
実際にスマートフォンで 200%まで拡大すると、こんな現象が起こりがちです。
- テキストの拡大(ズーム)が行われることで、
rem
単位を基準にした余白まで一緒に拡大してしまう。 - これにより、本来のコンテンツを囲む余白が過剰に広くなってしまう。
- 結果として、コンテンツ表示エリアが極端に狭まり、レイアウト崩れや見づらい状態になってしまう。
※ ただ現実問題として、WCAG準拠が義務とされている海外の政府系サイトなどをみてもあまり対応がされていない印象です。 おそらく費用対効果や、そもそもスマートフォンで200%に拡大というニーズ自体がほとんどないと思われることから、現場レベルでは「やらなくても大きな問題にはならない」と見なされているのかもしれません。
この問題を解消するには?
テキスト拡大の影響を受ける rem
ではなく、vw
のようなビューポート基準の単位を使い、ズームしても余白を必要以上に大きくしないことが重要です。
でもvw
のみで指定すると clamp()
の「最小値・最大値を制御できる」というメリットを活かせません。
そこで登場するのが min()
関数です!
min()
は渡された値の中で「最も小さい値」を採用します。この特性を利用して、通常時と拡大時で適用される値を切り替えます。
書き方はこうです。
--spacing-lg: min(
calc(var(--spacing-lg-min) / var(--clamp-viewport-min) * 100vw),
clamp(
calc(var(--spacing-lg-min) / var(--clamp-base) * 1rem),
calc(var(--spacing-lg-intersection) * 1rem + 100 * var(--spacing-lg-slope) * 1vw),
calc(var(--spacing-lg-max) / var(--clamp-base) * 1rem)
)
);
これを375 CSS pxで表示した時のminの最初の値は--spacing-lg-min
の値である24pxをvwにした6.4vwになります。
clamp()
の最小値は1.5rem = 24pxですが、200%拡大時には48pxになるため、左側の6.4vwの方が小さくなり、こちらが適用されることになります。
つまり、
- 通常時(100%表示):
clamp()
の値の方がvw
基準の値よりも小さくなります。min()
は小さい方を採用するので、clamp()
が効いて画面幅に応じて可変で表示されます。
- 拡大時(200%表示):
clamp()
内のrem
が拡大によって大きくなり、vw
基準の値の方が小さくなります。vw
基準の値が適用され、余白が画面幅に対する比率で固定されるため、過剰な拡大を防げます。
これで、「レスポンシブな利便性」と「アクセシビリティへの配慮」を両立できるわけです!
※ ちなみにPC表示では余白も含めて拡大された方がデザインのバランスが保たれることが多いため、--clamp-viewport-min
を基準としてスマートフォンのみ適用としています。
実際の挙動を比較
上が clampのみ、下が min+clamp です。
スマホで200%に拡大をすると、違いがはっきり分かります。
※ iOS Safariはアドレスバー左のアイコンから、Android Chromeは右上のアイコンから拡大ができます。
まとめ
- clamp() はレスポンシブな余白調整に便利
- ただし拡大表示すると余白まで拡大し、レイアウト崩れの可能性がある
- min() と組み合わせることで、通常時は可変・拡大時は制御というバランスの取れた対応が可能になる
WCAG対応を意識するなら、こうした実装を取り入れておくと安心ですね。 「スマホで200%の拡大まで気を配る」実装はまだ珍しいですが、普段の見やすさを損なわずに、アクセシビリティ面のリスクも抑えられます。
ちなみに、余白以外の細かい調整をしたい場合は、狭いブレイクポイントのメディアクエリを指定する方法もあります。
@media (max-width: 239px) {
/* 拡大時に適用したいCSS */
}
例えば 幅375pxの画面を200%に拡大した場合、ブラウザはビューポートの幅を「半分のサイズ(約188px)」として計算します。そのため、この狭い条件にマッチさせることができるんです。
※ 239px という値は一例で、将来の画面大型化の余地を残しつつ、通常表示には影響が出ないように調整して決めるとよいでしょう。
「clamp() + min()」と「狭いブレイクポイントのメディアクエリ」を組み合わせれば、より柔軟で安心なアクセシビリティ対応が可能になります!
DTPからWebの世界へ飛び込み、気づけばマークアップもフロントエンドもディレクションもアクセシビリティもこなす"技の仙人"。リベロジック創業期からマルチに活躍し、今や社内の生き字引的存在。最近は「アクセシビリティ対応、もっとAIに頼れないかな?」と、プロンプトを駆使した効率化の探究にハマり中。技術も思考も、まだまだ進化中
フタさん
マークアップエンジニア/フロントエンドエンジニア/ウェブアクセシビリティエンジニア/ウェブディレクター