レスポンシブ対応にメディアクエリなしで、CSSの関数で定義!border-radiusの値を変えるテクニック
Post on:2021年10月14日
border-radiusの値をデスクトップとスマホで変える、例えばビューポートが大きくてマージンがある場合は8pxで角丸にし、ビューポートが小さくてマージンがない場合は0pxで矩形にする。
メディアクエリで簡単に実装できると思うかもしれません。しかし、ビューポートのサイズが小さく、マージンがある場合に8pxの角丸になりません。
この難しい条件をCSSの関数で実装するテクニックを紹介します。
Conditional Border Radius In CSS
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
はじめに
私はデベロッパーがどのようなCSSを書いているか知りたいと常に思っています。少し前に、facebook.comのメインのフィードにあるカードコンポーネントに実装されているborder-radiusの値が非常に興味深いことに気がつきました。
私はこの小さな発見をツイートしました。
While inspecting Facebook's CSS, I found that the border radius has a max() and min() CSS comparison function.
This will compute to 8px / 8px in all cases. It's unclear why it's being used with such a large number (9999).
Any thoughts? 🧐 pic.twitter.com/g6KLsEVrOl
— Ahmad Shadeed (@shadeed9) October 3, 2021
Miriam Suzanneからリプライがありました。
常に8pxですか?私にはその数学はトグルのように見えますが、ある時点((100vw - 4px) - 100%)でマイナスになり、9999から-9999に切り替わると、負の値になる可能性がありませんか?そうすると、値が0に反転するのでは?基本的に、ビューポートのフルサイズから4px以内であれば、角丸の半径は削除されます。
その数時間後、FacebookのFrank Yanからリプライがありました。
これはカードがビューポートの幅いっぱいに表示されているときに、8pxを0pxに反転させるための条件文です。
これは素晴らしいアイデアだと思いませんか?
私は最初、これは一種のバグか何かの間違いではないかと思いました。しかし、私は間違っていました。この記事では、その問題点を明らかにし、解決策がどのように機能するかを説明します。
border-radiusの値をスマホとデスクトップで変えるテクニック
border-radiusが8pxのカードコンポーネントがあるとします。カードにマージンがない場合、またはビューポートの幅いっぱいにカードが表示されている場合は、border-radiusを0pxに反転させたいと思います。
左: スマホでは0pxに
右: デスクトップでは8pxに
これはCSSのメディアクエリでborder-radiusを削除することで実装できます。
1 2 3 4 5 |
@media (min-width: 700px) { .card { border-radius: 8px; } } |
しかし、これには限界があります。
何らかの理由で、ビューポートサイズが450px未満でborder-radiusを有効にしたい場合は、CSSクラスのバリエーションを作成し、再びメディアクエリで定義する必要があります。
1 2 3 4 5 |
@media (max-width: 450px) { .card--rounded { border-radius: 8px; } } |
実装方法の解説
それでは、Facebookのチームが実装した素晴らしいアイデアを真似て、実装してみます。
1 2 3 4 5 |
if (cardWidth >= viewportWidth) { radius = 0; } else { radius = 8px; } |
このロジックをCSSで実装するには、CSSの比較関数を使って2つの値を比較する必要があります。比較関数をご存じない方は、下記をご覧ください。
このテクニックは、Heydon Pickeringの記事「The Flexbox Holy Albatross」にヒントを得ています。これをFacebookのNaman Goelはborder-radiusで機能するようにしました。
1 2 3 |
.card { border-radius: max(0px, min(8px, calc((100vw - 4px - 100%) * 9999))); } |
上記のCSSについて詳しく見てみましょう。
- 0pxとmin()の計算値があり、この2つの値をmax()関数が比較し、大きい方の値が使用されます。
- min()関数では、8pxとcalc((100vw - 4px - 100%) * 9999)の計算値を比較します。これにより、非常に大きな正または負の数値が得られます。
- 9999は、強制的に0pxか8pxのどちらかにするための大きな数値です。
次に、calc()の魔法を説明しましょう。
魔法は値が100%の時に起こり、2つの異なるシナリオに基づいています。
- その要素を含む要素(親やラッパーなど、CSSでの呼び名は問いません)の100%と等しくすることができます。
- または、カードがビューポートの幅いっぱいに表示される場合(例えばスマホ)は、100vwと等しくすることができます。
calc()の魔法
なぜ、9999を使用するのか
この数字にすごいパワーがあるとか、そういうことではありません。エッジケースを避けるためです。このことを思い出させてくれたTemani Afifに感謝します。
ビューポートの幅が375pxで、コンテナの幅が365pxだとします。この値を式に代入すると、下記のようになります。
1 2 3 4 5 |
.card { border-radius: max(0px, min(8px, calc(375px - 4px - 365px))); /* will result to */ border-radius: max(0px, min(8px, 6px)); } |
calc()の計算値は6pxになりますが、これは望んだ値ではありません。border-radiusの値には、0pxか8pxのどちらかにしたいのです。これを実現するために、結果を9999のように実際に使用される可能性が低い大きな数字で倍にします。
1 2 3 4 5 6 |
.card { border-radius: max(0px, min(8px, calc((375px - 4px - 365px) * 9999))); /* will result to */ border-radius: max(0px, min(8px, 59994px)); } |
このCSSに基づいて、ブラウザはmin()関数から8pxを選び、次にmax()関数から同じ値を選びます。
最初のシナリオに基づいた例を見てみましょう。
幅が1440pxのビューポートがあり、カードコンポーネントは700pxのコンテナ内に配置されています。
最初のシナリオに基づいた例
結果の値に9999を掛けると7359264となり、大きな乱数となります。この場合、CSSはブラウザで次のようになります。
1 2 3 |
.card { border-radius: max(0px, min(8px, 7359264px)); } |
min()関数があるので、2つの値を比較して、最小値である8pxが選ばれます。そして、max()関数で、8pxの方が勝ちます。これが、このCSSの巧妙な使用例です。
1 2 3 |
.card { border-radius: 8px; } |
次に、カードがビューポートの幅いっぱいに表示される場合です。これは、スマホのビューポートで見ることができます。コンテナの幅とビューポートの幅が同じであることに注目してください。
カードがビューポートの幅いっぱいに表示される場合(スマホ)
値に9999を掛けると-39996pxとなり、マイナスの値となります。CSSはブラウザで次のようになります。
1 2 3 |
.card { border-radius: max(0px, min(8px, -39996px)); } |
さあ、お楽しみの時間です!
ブラウザは2つの質問をします。
- どちらの値が小さいですか?
8pxと-39996pxのどちらでしょうか?
結果は、-39996pxです。 - どちらの値が大きいですか?
0pxと-39996pxのどちらでしょうか?
結果は、0pxです。
1 2 3 |
.card { border-radius: 0px; } |
これをみて、あなたはどう思いましたか?
CSSの比較機能をこのように巧妙に使用していることに、私は驚いています!
Temani AfifとLiad Yosefが提案したように、CSSのclamp()を使用することで、これを次のレベルに上げることができます。古いバージョンのSafari(v12など)ではサポートされていないので、Facebookチームは使用しなかったと思います。
1 2 3 |
.card { clamp(0px, (100vw - 4px) - 100%, 8px) } |
実際の動作は、下記のデモでご覧ください。
See the Pen
Border radius / FB by Ahmad Shadeed (@shadeed)
on CodePen.
終わりに
この記事があなたのお役に立てれば幸いです。
コメントや提案があれば、@shadeed9までお願いします。
sponsors