面倒な計算はもう不要!CSSのclamp関数の活用テクニック

CSSのclamp関数を使用する際に、推奨値の計算をしたり、代わりにジェネレータを使用するのは結構面倒だな〜、と思ってclampを簡単に出力できるscss関数を作ってみました。
ジェネレータはググると色々出てきますが、remの値しか出力してくれないものが多く、pxで出せるものを探すのも結構面倒です。
clamp関数とは何かについては、以下のリンクで詳しく解説されています。
CSSの比較関数が便利すぎる! min(), max(), clamp()の使い方を詳しく解説 | コリス (coliss.com)

2023/7/2追記:
使い勝手の改善と最大値と最小値がマイナスの場合などでも意図した通りの結果が出力されるように改善しました。

  • 使い勝手の改善:
    • 最小値と最大値が適用される画面幅を固定ではなく、オプション引数で指定可能に変更
  • 特殊な状況での出力結果の修正:
    • グラフの傾斜が逆になる(右に向かって下がる)場合に発生していた不具合を解消

コード

指定した最小値と最大値に応じて、適用される画面幅に合わせた値を返します。

/*
 * 機能:clamp関数の文字列を返す
 * 引数:$size-at-min-width サイズ(フォントサイズなど)の最小値(pxやremなどの単位は必須)
 *    $size-at-max-width サイズ(フォントサイズなど)の最大値(pxやremなどの単位は必須)
 *    $min-width [省略可]サイズが最小値に到達する画面幅
 *    $max-width [省略可]サイズが最大値に到達する画面幅
 * 使用方法:
 *  デフォルトの最小画面幅$min-width-defaultと最大画面幅$max-width-defaultを事前に指定しておく
 *  最小幅と最大幅をデフォルト以外にしたい場合は、引数にそれぞれ単位付きの画面幅を入れる
 */
$min-width-default: 568px; // デフォルトの最小画面幅
$max-width-default: 1024px; // デフォルトの最大画面幅
@function clamp-calc(
  $size-at-min-width,
  $size-at-max-width,
  $min-width: $min-width-default,
  $max-width: $max-width-default
) {
  // グラフの斜度
  $slope: calc(($size-at-max-width - $size-at-min-width) / ($max-width - $min-width));

  // グラフのy方向開始点の調整
  $y-axis-intersection: -1 * $min-width * $slope + $size-at-min-width;
  // 小数点以下3桁で四捨五入
  $y-axis-intersection: round-decimal($y-axis-intersection, 3);
  // $slopeが単位なしの数値なので、$return-valueで使用するために単位をvwにする
  $slope-vw: calc($slope * 100vw);
  // 小数点以下3桁で四捨五入
  $slope-vw: round-decimal($slope-vw, 3);

  // グラフの傾斜が逆方向になる場合、clamp関数の引数の最小値と最大値を入れ替える
  // ※例えば、マイナスマージンで画面幅が広い時に絶対値での数値が大きい場合に発生する。
  //  入れ替えないと、画面幅に関係なくサイズの最小値が適用されてしまう。
  @if $size-at-max-width < $size-at-min-width {
    $temp-max: $size-at-max-width;
    $temp-min: $size-at-min-width;
    $size-at-max-width: $temp-min;
    $size-at-min-width: $temp-max;
  }

  // 最終的な結果を格納
  $return-value: clamp(#{$size-at-min-width}, #{$y-axis-intersection} + #{$slope-vw}, #{$size-at-max-width});
  @return $return-value;
}
// 小数点以下の指定した桁数で四捨五入する関数
@function round-decimal($value, $decimal-place) {
  // 四捨五入する値($value)を10の小数点以下桁数累乗倍する
  $temp-value: calc($value * pow(10, $decimal-place));
  // 累乗倍した数値で四捨五入(roundは小数点以下の指定ができない)
  $temp-value: round($temp-value);
  // 四捨五入した値を再度同じ10の累乗倍の数値で割った数値を返す
  @return calc($temp-value / pow(10, $decimal-place));
}
// 累乗の関数(指数は正の整数のみで有効、負の指数は対象外)
@function pow($number, $exponent) {
  $value: 1;

  @if $exponent > 0 {
    @for $i from 1 through $exponent {
      $value: $value * $number;
    }
  }
  @return $value;
}

round-decimal()とpow()関数は無くても機能します。
scssの標準の関数では小数点以下の桁数を指定した四捨五入ができないのでround-decimal()を作りました。
そのround-decimal()の中で累乗する必要があるので、pow()も作りました。
※powはpower(累乗)の略です。
四捨五入しないと、小数点以下の数値が長くなって読みにくいものの、実際の画面表示上は四捨五入してもしなくても目視でわかる差が出ないので私は四捨五入しています。

// 四捨五入した場合
font-size: clamp(16px, 11.018px + 0.877vw, 20px);
// 四捨五入しない場合
font-size: clamp(16px, 11.0175438596px + 0.8771929825vw, 20px);

使い方

事前に関数の外で、最小値と最大値が適用される画面幅のデフォルト値をそれぞれ定義しておきます。以下は、上のコードの抜粋です。

$min-width-default: 568px; // デフォルトの最小画面幅
$max-width-default: 1024px; // デフォルトの最大画面幅

あとは、scssのコードの中で以下のように引数に最小値と最大値を指定して使います。
font-sizeだけでなく、paddingやmarginなどでも使えます。(私はこれでメディアクエリを使う頻度が激減しました)

font-size: clamp-calc(16px, 20px);

最小値と最大値が適用される画面幅をデフォルト値以外にしたい場合は、引数にそれぞれの値を追加します。

// 第3引数が最小値が適用される画面幅(この例では375px)
// 第4引数が最大値が適用される画面幅(この例では1500px)
font-size: clamp-calc(16px, 20px, 375px, 1500px);

関数の説明(一部のみ)

clampを使うにあたり気になったのは、指定した画面幅以下になってfont-sizeが小さくなり始めてから最小値になるまでの変化が早いことです。
理想としては、2点のブレークポイントで綺麗に最大値と最小値になると気持ちいいです。
気にしなければどうでも良いのですが、細かいことが気になるタチなので、今後clampを使うたびにモヤモヤするのもなーと思いまして。
clamp-calc()の中に出てくる変数でわかりにくい $slope と $y-axis-intersection をグラフに表してみました。

$y-axis-intersection は、昔学校で習った [y = 0.5x + 10] のような方程式の[10]の部分ですね。
ちなみに、今回の関数でこのような方程式の[x]に当たるのが画面幅(vw)で、[0.5]は $slope です。

最大値と最小値が逆転してして、グラフの傾斜($slope)が右に向かって下がる場合に、clamp関数の引数の最大値と最小値を入れ替える必要があります。
$slopeなどの計算では、最大値と最小値の位置付けは変わりませんが、clamp関数に渡す引数では、最大値と最小値を入れ替えないと、ずっと最小値が適用されてしまうという不具合があったので以下のコードを追加しました。
※例えば、マイナスマージンで最大値と最小値が両方マイナスの場合に、絶対値では最大値の方が大きくても、相対値では最大値の方が小さい状態になります。
もう少し具体的な例だと、margin-topで画面幅が広い時には -48px、画面幅が狭い時には -16px にしたい場合などです。

  // グラフの傾斜が逆方向になる場合、clamp関数の引数の最小値と最大値を入れ替える
  // ※例えば、マイナスマージンで画面幅が広い時に絶対値での数値が大きい場合に発生する。
  //  入れ替えないと、画面幅に関係なくサイズの最小値が適用されてしまう。
  @if $size-at-max-width < $size-at-min-width {
    $temp-max: $size-at-max-width;
    $temp-min: $size-at-min-width;
    $size-at-max-width: $temp-min;
    $size-at-min-width: $temp-max;
  }

おわりに

最近になってやっとclampを使ったら、あまりの便利さに驚愕して、使ってこなかった自分を呪いました。
でも推奨値の計算はもとより、y方向の開始点を計算するのがかなり面倒だったのでマルっと関数にしたら、思った通りめちゃくちゃ楽になりました。
使えば使うほど、メディアクエリの使用頻度が激減して、clamp関数の偉大さを実感する日々です。

参考にしたサイト

感謝です。
Easy CSS Clamp SCSS Mixin – DEV Community
Power Function | CSS-Tricks – CSS-Tricks