比較関数clamp(), min(), max()を使いこなす

比較関数clamp(), min(), max()を使いこなす

はじめに

この記事では、CSSの比較関数clamp() min() max()使った具体的な実装方法を備忘録として残しておきたいと思います。

比較関数とは

clamp()min()max()関数はCSS の値と単位 — CSS Values and Units Module Level 4によると複数の値を比較し、使用された関数に基づいてそれらの1つを表すことから比較関数と呼ばれるそうです。

min()

min関数は2つのパラメータを比較して最も小さい値を返します、
下の例の場合、引数の2vw20pxを比較して、小さいほうの値になります。
つまり、最大値を設定するのに使用することができます。

//min()の構文
プロパティ: min(値,  値)

//例
p {
  font-size: min(2vw, 20px);
}

この場合、第一引数にvwを指定しているためビューポート幅に依存します。
仮にビューポート幅が800pxだった場合、2vw16px相当になり、20pxよりも2vwのほうが小さいのでmin()関数は2vwの値を返します。
逆にビューポート幅が1200pxだった場合、2vw24px相当になり、2vwよりも20pxのほうが小さいので、返ってくる値は20pxになります。
ですので、この場合のfont-sizeはどんなビューポート幅でも20px以下となります。

max()

max()関数は2つのパラメータを比較して最も大きい値を返します、
つまり、最小値を設定するのに使用することができます。

//max()の構文
プロパティ: max(値,  値)

//例
p {
  font-size: max(2vw, 20px);
}

この場合、引数の2vw20pxを比較して、大きいほうの値になります。

仮にビューポートの幅が700pxだった場合、2vw14px相当になります。2vwよりも20pxのほうが大きいのでmax()関数が返す値は20pxになります。
次にビューポートの幅が1200pxだった場合、2vw24px相当になります。20pxよりも2vwのほうが大きいので、返ってくる値は2vwになります。
ですので、この場合のfont-sizeはどんなビューポート幅でも20px以上となります。

clamp()

clamp()関数は、引数に3つの式をとります。1つ目は最小値、2つ目は推奨値、3つ目は最大値を指定します。推奨値と最小値・最大値を比較し、3つのうちのいずれかの値が、プロパティの値として適用されます。

//clampの構文
プロパティ: clamp(最小値, 推奨値, 最大値)

//例
p {
  font-size: clamp(18px, 5vw, 40px);
}

この場合

  • 最小値18px
  • 最大値40px
  • 18pxより大きく40px未満の場合推奨値の5vwが適用されます。

これらCSSの比較関数を使用することで、レイアウトやコンポーネントのスタイルに%emvwといった相対的な値を使いたい場面で、最小値や最大値、もしくはその両方を設けることができます。

対応ブラウザとフォールバック

2023年3月現在、主要ブラウザにサポートされています。

clamp(), min(), max()の対応ブラウザ

clampに関しては現在、Safariの11.1-13とiOS Safariの11.3-13.3ではサポートされていません。
これらのブラウザにもclampを対応させたい場合は、フォールバックする必要があります。

clamp(最小値, 推奨値, 最大値) = max(最小値, min(推奨値, 最大値))となるので、@supportsを使いclamp()に対応していない場合は、min()max()を組み合わせた方法でフォールバックします。

p {
  font-size: clamp(10px, 2vw, 30px);
}
//clamp()未対応ブラウザ用のフォールバック
@supports not (font-size: clamp(10px, 2vw, 30px)) {
  p {
    font-size: max(10px, min(2vw, 30px));
  }
}

clamp()関数のちょうどいい推奨値を計算するmixin

clamp()を使った実装では推奨値を最小値から最大値の間をちょうどいい感じにつなげるため、vwを調整する必要があります。普通に毎回計算するのは面倒ですので引数の値から自動で計算するmixinを定義しました。

/*
  clamp()関数の推奨値を1次関数を用いて計算
  $property: プロパティ
  $minBp: 下限ブレークポイント
  $maxBp: 上限ブレークポイント
  $minVal: 最小値
  $maxVal: 最大値
  
  @include clampFunction('プロパティ', 下限ブレークポイント, 上限ブレークポイント, 最小値, 最大値);
*/
@mixin clampFunction($property, $minBp, $maxBp, $minVal, $maxVal) {
  $a: round(100 * ($maxVal - $minVal) / ($maxBp - $minBp));
  $b: round($minVal - ($minBp / 100) * $a);
  $vw: calc(#{$a}vw + #{$b}px);

   // フォールバック
  @supports not (#{$property}: clamp(#{$minVal}px, #{$vw}, #{$maxVal}px)) {
    #{$property}: unquote('max(#{$minVal}px, min(#{$vw}, #{$maxVal}px))'); 
  }
  #{$property}: clamp(#{$minVal}px, #{$vw}, #{$maxVal}px);
}

@include clampFunction('プロパティ', 下限ブレークポイント, 上限ブレークポイント, 最小値, 最大値);

引数にこれら5つの値を設定して呼び出します。

// 呼び出し
.section-title {
  @include clampFunction("font-size", 375, 1280, 16, 50);
}

// ビューポート幅が375px(下限)の時に16px
// ビューポート幅が1280px(上限)の時に50px
// ビューポート幅が375〜1280pxの間はちょうどいい感じに可変

出力CSS

// 出力CSS
.section-title {
  font-size: clamp(16px, calc(4vw + 1px), 50px);
}

計算方法

計算方法としては一時関数を使います。
公式y=ax+bを使って、

  • 下限ブレークポイント、上限ブレークポイントをx
  • 最大値、最小値をy

として2つの式を作り、連立方程式でaとbの値を求めます。
また、計算結果をvwとして出したいのでこの時点でブレークポイントにおける1vwの値をxに代入します。

上のコードの場合

  • 最大フォントサイズを50px
  • 最小フォントサイズを16px
  • 下限ブレークポイントを375px
  • 上限ブレークポイント1280px

として計算しています。

16 = 3.75 × a + b
50 = 12.80 × a + b

/// 最小値 = (下限bp / 100) × a + b 
// 最大値 = (上限 /bp 100) × a + b

連立方程式でaを求める(上のmixinでは小数点以下をraundで丸めています。)

16 − 3.75 × a = 50 − 12.80 × a
(12.80 − 3.75) × a = 50 − 16
a = 100 × (50 − 16) / (12.80 − 3.75)
a = 375.69060...

// 最小値 − 下限bp × a = 最大値 − 上限bp × a
// (上限bp − 下限bp) × a = 最大値 − 最小値
// a = 100 × (最大値 − 最小値) / (上限bp − 下限bp)

連立方程式で求めたaの値を代入しbを求める

b = 16 − (375 / 100) × 4
b = 1

//b = 最小値 − (下限bp / 100) × a

a=4、b=1なので、clamp()の第2引数の式はcalc(4vw + 1)となります。

.section-title {
    font-size: clamp(16px, calc(4vw + 1px), 50px);
}

デモ①: 動的な見出し

デモ②: 動的な余白

デモ③: セクション間の垂直なpadding

最小値と基準値を設定する

max()関数を使用して最小値を設定したのち、ウィンドウ幅を広げていった時、どのくらいのビューポート幅でどのくらいのサイズになるかわからない時があります。
例えば、「ビューポート幅1280pxで値が50pxに達する」というような基準を設けたい場合に、
pxをvwに変換するfunctionを組み合わせることで実現できます。

pxをvwに変換するfunctionとmax()関数を組み合わせる

/*
  pxをvwに変換するfunction
  $size: サイズ
  $viewport: ビューポート幅
*/
@function _getVw($size, $viewport: 375) {
  $rate: calc(100 / $viewport);
  @return $rate * $size * 1vw;
}

このfunction内の計算方法をmixinを使ってmax()関数と組み合わせます。

/*
  max()関数で最小値とウィンドウ幅を広げていった時の基準値を出力するmixin
  $property: プロパティ
  $minVal: 最小値
  $baseVal: 基準となる値
  $viewport: 基準となるビューポート幅
*/
@mixin maxFunction($property, $minVal, $baseVal, $viewport) {
  $rate: calc(100 / $viewport);
  $result: calc($rate * $baseVal * 1vw);

  #{$property}: unquote('max(#{$result}, #{$minVal}px)');
}

// 最小値16px ビューポート幅1280pxで50pxに達する
.section-title {
  @include maxFunction('font-size', 16, 50, 1280);
}

出力CSS

.section-title {
    font-size: max(3.90625vw, 16px);
}

デモ: 最小値と基準値を設定する

おわりに

比較関数、vwcalc()などを組み合わせるとメディアクエリーでの書き換えなしでビューポート幅に応じて余白やfont-sizeを相対的に定義することができます。

一見すると便利で、全てこの定義方法で実装したくなりますが、18px -> 24pxなど値が大きく変化しない場合などはclampなどを使うと逆にコードがややこしくなってしまうのでよく検討してから使用したほうが良いと思いました。

余白やfont-size以外にもbox-shadowやborer-radiuslinear-gradient()などで利用することができるみたいなので、また機会があれば記事にしたいと思います。