clamp関数を数学的に理解したい人のための教科書

# 0．はじめに
Web制作をしているとウィンドウサイズに応じてテキストの大きさを調整したいことが頻繁にあります。そのような場面で**ブレイクポイントを増やさずに**・**部分的に**レスポンシブ対応できるcss関数がclamp関数です。

また**リキッドデザイン**のマークアップにおいてフォントサイズの扱いは面倒ですが、clamp関数を用いることで**ウィンドウサイズに応じて流動的に可変させる**ことが出来ます。

[【レスポンシブデザインとリキッドデザイン？それぞれの特徴とメリット、デメリット！】](https://www.m-hand.co.jp/design/8504/)

ここではclamp関数の有用性・使い方・scss関数化について説明し、明日から現場で使える知識として理解していただくことを目的としています。

[【clamp関数のブラウザ対応状況】](https://caniuse.com/?search=clamp)


# 目次
[1. clamp関数とは](#1clamp関数とは)
[2. clamp関数の類似関数であるmin関数とmax関数](#2clamp関数の類似関数であるmin関数とmax関数)
[3. clamp関数の引数の値の決定方法](#3clamp関数の引数の値の決定方法)
[4. clamp関数のscss関数化 ](#4clamp関数のscss関数化)
[5. まとめ](#5まとめ)

# 1.clamp関数とは 
[clamp関数](https://developer.mozilla.org/ja/docs/Web/CSS/clamp) は数学関数に分類されるcss関数の1つです。ここではいくつかのポイントに絞って紹介しますが、詳しいことが知りたい場合は公式ドキュメントをご参照ください。

## 1-1.clamp関数の引数
clamp関数は数学関数ですので当然引数が存在します。
- **MIN**：最小値（絶対単位）
- **VAL**：推奨値（相対単位）
- **MAX**：最大値（絶対単位）

:::note warn
ここでは **絶対単位** はpxやrem等を想定しており、 **相対単位** はvwを想定しています。
:::
clamp関数はこれら3つの引数を持ち、`clamp(MIN, VAL, MAX)`の形で記述されます。

## 1-2.clamp関数の挙動
clamp関数は「ウィンドウサイズに対応してテキストの大きさを調整できる関数」ですが、どのようにしてその役割を果たしているのでしょうか。

clamp関数は現在のウィンドウサイズによって3つの引数から適切な値を返す関数です。つまり「現在のウィンドウサイズは〇pxだから適切な値は3つのうち、これ！」と自分で判断して値を返してくれる関数であり、メディアクエリの挙動とよく似ています。そのため3つの値を選択する条件を明確に提示してあげれば、特定のテキストに対してメディアクエリによるフォントの指定と同等かそれ以上のことが出来るようになります。

## 1-3.clamp関数の返り値の選択条件
clamp関数を効果的に使用するためには返り値がどういった条件で選択されているかを知る必要があります。条件は以下の通り。
1. $MAX_{(px)} \geq VAL_{(px)}$ のとき、返り値は $MAX_{(px)}$ 
2. $MAX_{(px)} < VAL_{(px)} < MIN_{(px)}$ のとき、返り値は $VAL_{(vw)}$ 
3. $MIN_{(px)} \geq VAL_{(px)}$ のとき、返り値は $MIN_{(px)}$ 

:::note
それぞれの引数の値を以下のように表記します。
- VALそのものの値を $VAL_{(vw)}$
- VALを絶対単位に変換した値のことを $VAL_{(px)}$
- MAX、MINの値をそれぞれ $MAX_{(px)}$、$MIN_{(px)}$

:::

# 2.clamp関数の類似関数であるmin関数とmax関数
clamp関数と同じ数学関数に[min関数](https://developer.mozilla.org/ja/docs/Web/CSS/mix)と[max関数](https://developer.mozilla.org/ja/docs/Web/CSS/max)があります。
これらはそれぞれ`min(VAL, MIN)`、`max(VAL, MAX)`と表記し、表記の仕方からclamp関数とよく似ていることが分かります。

## 2-1.clamp関数とmin関数、max関数の使い分け
まず前提としてmin関数、max関数で出来ることは基本的にclamp関数でもすることが出来ます。その上で使い分けをするのであればclamp関数はMAXとMINが与えられているという条件下でVALを算出するのに対し、min関数とmax関数はVALが先に決まっているという条件下で有効に働きます。

例えば「ここは12vwで入れる」ということが決まっているテキストに対して、一定以上（もしくは以下）のウィンドウサイズではそれ以上大きく（もしくは小さく）ならないでほしい時に有効に使えます。このシチュエーションは%単位で入れているコンテンツに対して`max-width()`や`min-width()` でコンテンツの大きさに制限をかけるのと非常によく似ています。

そのためmin関数とmax関数は、テキスト版`max-width()` `min-width()` と考えて使用するとclamp関数と比べて直感的で使いやすいように思います。

# 3.clamp関数の引数の値の決定方法
前の章でclamp関数の有用性について以下のように述べました。

> （clamp関数が）3つの値を選択する条件を明確に提示してあげれば、特定のテキストに対してメディアクエリによるフォントの指定と同等かそれ以上のことが出来るようになります

ただし、これは言い換えると**引数の選択基準が曖昧であれば意図した条件下で意図した値を返すことは難しい**ということです。実際はMAXとMINの値さえ適切な値で設定されていればVALの値は決定基準が曖昧でもよしなになってくれます。しかしWeb制作の値の決定において、感覚もしくは主観が介入することはあまり好ましくありません。そのため明確な意図を持って値の決定が行える点においてVALの算出方法を数学的に理解しすることは価値のあることでしょう。

またVALの値の決定に感覚や主観が介入することは制作効率の悪化にも繋がります。そのためclamp関数を使用する場合は後述するscss関数化が望ましいでしょう。VALの算出方法の理解はclamp関数のscss関数を理解することの助けにも繋がります。

## 3-1.MAXの値の決定方法
MAXは基本的に**PC系統のデザインカンプのフォントサイズをそのまま採用**します。例外もありますが、基本的にPC系統のデザインカンプよりもフォントサイズを大きくしたいというシチュエーションは考えづらいためです。そのため以降はPC系統のデザインカンプのフォントサイズをMAXとして採用し、MAXを基準として他の値を決定していきます。

:::note warn
デザインカンプのフォントサイズを超えてほしいケースは稀である
よって $MAX_{(px)}$ = デザインカンプのフォンサイズ とする
:::

## 3-2.MINの値の決定方法
MINの値は**clamp関数を記述するブレイクポイント内で最もウィンドウサイズが小さい時点でのフォントサイズ**を採用します。例えばブレイクポイントを1440pxから768pxまでと設定したとき、clamp関数のMINは768px時点でのフォントサイズを指定します。


サイト全体でclamp関数を頻繁に使う場合は、MINの値の選択基準が全くないとウィンドウサイズが小さい時に**テキストのジャンプ率が低いサイト**になってしまいます。そのため最低限ジャンプ率を崩さない程度の基準は必要です。具体的には「MAXの80%以下にはしない」や「MAX-6px以下にはしない」のような基準が挙げられます。

またスマートフォン系統のデザインカンプのフォントサイズもMINの値の選択肢の1つです。MAX同様、**MINの値がスマートフォン系統のデザインカンプより小さな値を取ることはない**ため、タブレット系統とスマートフォン系統のフォントサイズが同じ値を取る場合に有効です。

逆にタブレット系統のデザインカンプが用意されており、スマートフォン系統と異なるフォントサイズが設定されている場合はタブレット系統のフォントサイズをMINの値に採用するといいでしょう。


:::note warn
PC系統のフォントサイズを$PC_{fz}$、タブレット系統のフォントサイズを$TB_{fz}$、スマートフォン系統のフォントサイズを$SP_{fz}$とすると、常に以下の関係が成り立ちます。
$SP_{fz} \leq MIN_{(px)} \leq TB_{fz}<PC_{fz}=MAX_{(px)}$

:::

## 3-3.VALの算出方法
VALの決定方法は以下の2種類が考えられます。

**1.** $WS_{start} = BP_{max}$ かつ $WS_{end} = BP_{min}$の場合
**2.** $WS_{start} \neq BP_{max}$ または $WS_{end} \neq BP_{min}$の場合

:::note
ウィンドウサイズとブレイクポイントに関して以下の書き方を使用します。
- ウィンドウサイズについて
    - VALを参照させはじめたいウィンドウサイズを$WS_{start}$
    - VALの参照しおわりたいウィンドウサイズを$WS_{end}$

- ブレイクポイントについて
    - ブレイクポイントの上限値を$BP_{max}$
    - ブレイクポイントの下限値を$BP_{min}$
:::


言い換えると
**1. ブレイクポイントの上限値から下限値まで**常に同じ変化量で単調減少する場合
**2. 特定のウィンドウサイズから特定のウィンドウサイズまで**は常に同じ変化量で単調減少し、それ以外ではフォントサイズが変化しない場合

:::note

ブレイクポイント1400px~768px内で1200px~768pxの間だけclamp関数にVALを参照させたい場合
$BP_{max}：1400px$　$BP_{min}：768px$
$WS_{start}：1200px$　$WS_{end}：768px$

:::


### 3-3-1.VALの算出方法と一次関数
VALの算出方法は一次関数の変化の割合と切片の計算問題に置き換えることができます。これはVALが`calc()`のように計算式として指定することができ、その形が一次関数と同じであることに起因します。
$VAL_{(vw)}$は以下の形で指定し、ここでの相対単位はvwであると仮定します。

　$VAL_{(vw)}\quad=\quad$ 相対単$位_{(vw)}\quad+\quad$絶対単$位_{(px)}$

また$VAL_{(vw)}$ が実際にブラウザ上で計算され$VAL_{(px)}$ に変換されるとき、以下のような計算式を用います。

　$VAL_{(px)}\quad=\quad VAL_{(vw)}\quad \times\quad$現在のウィンドウサイ$ズ_{(px)}\quad +\quad$絶対単$位_{(px)}$

:::note warn
実際は$VAL_{(vw)}$ が実際にブラウザ上で計算され$VAL_{(px)}$ に変換されるときは、百分率表記である$VAL_{(vw)}$を割合に変換するために0.01倍されますが、0.01は定数であり式の性質には影響を与えないのでここでは視認性を考慮し、省略しています。
:::

ここで$VAL_{(vw)}$と絶対単位は定数、ウィンドウサイズは変数であることから$VAL_{(px)}$は変化の割合が$VAL_{(vw)}$ で切片が絶対単位の一次関数であることがわかります。そのため、一次関数の変化の割合と切片の計算問題と同様に$VAL_{(vw)}$と絶対単位を求めることができ、適切なVALの値を算出することが出来ます。

### 3-3-2.一次関数の方程式問題のVALの算出方法への置き換え
> 問）点A（1400,32）と点B（768,18）を通る直線の方程式を求めなさい。

一般的な一次関数の計算問題です。まず変化の割合を計算し、求めた変化の割合を用いて切片を求めます。

> 問）最大フォントサイズを32px、最小フォントサイズを18pxとして、ウィンドウサイズ1400pxから768pxまでの間を一定の割合で変化するVALの値を求めたい。

clamp関数で適切なVALの値を算出したいときのシチュエーションです。問題の書き方は違いますが、上記の一次関数の計算問題と同様の方法で解くことが出来ます。大事なのは$WS_{max}$と$MAX_{(px)}$、$WS_{min}$と$MIN_{(px)}$をそれぞれ座標のようにセットとして扱うことです。なぜこれらを形式上の座標として扱うことができるのか、一次関数のグラフを用いて考えてみます。

### 3-3-3.グラフで理解するVALの算出方法と一次関数の関係
**1.** $WS_{start} = BP_{max}$ かつ $WS_{end} = BP_{min}$の場合
![IMG_2547.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2400266/69a17a60-f3c9-71e3-cf3d-42070949a325.png)

現在のウィンドウサイズとフォントサイズの関係をグラフにしました。VALの式は「幅1400pxでフォントサイズが32px」と「幅768pxでフォントサイズが18px」の2つの条件を満たす必要があります。そのためグラフにすると点(1400,32)と点(768,18)を通る一次関数として表すことが出来ます。

つまり、$WS_{max}$と$MAX_{(px)}$、$WS_{min}$と$MIN_{(px)}$をそれぞれ座標のようにセットに扱えるのは、VALの式が「幅1400pxでフォントサイズが32px」と「幅768pxでフォントサイズが18px」の2つの条件を満す直線であるためです。

**2.** $WS_{start} \neq BP_{max}$ または $WS_{end} \neq BP_{min}$の場合
![IMG_2548.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2400266/403bfcce-546f-d436-50d8-6e4f8cf298c1.png)

VALの算出には$WS_{start}$と$WS_{end}$さえ設定されていれば、その値は$BP_{max}$ 、$BP_{min}$である必要はありません。$WS_{start}$と$WS_{end}$ は $BP_{max} \leq WS\leq  BP_{min}$かつ$WS_{end} < WS_{start}$ さえ満たしていれば任意の値を取ることが出来ます。

上記のグラフは$WS_{start}=1200px$ と設定したときのVALの式を表しています。グラフから分かるように$WS$の値が変化すれば直線の方程式も変わるため、VALの値も$WS_{start} = BP_{(max)}$のときとは異なります。

より具体的な数値を用いた参考記事は[こちら](https://qiita.com/soundweaver/items/9b5a3a7c0b0e198f4570)。

### 3-3-4.VALの算出方法の一般化
後述のscss関数化のためにVALの算出方法を変数を用いて一般化する必要があります。


:::note
ここでは$VAL_{(vw)}$を構成する相対単位と絶対単位を以下のように表します。
- 相対単位（一次関数の変化の割合）の値を $RU_{(vw)}$
- 絶対単位（一次関数の切片）の値を $AU_{(px)}$
:::

```math
RU_{(vw)}= \frac{MAX_{(px)}-MIN_{(px)}}{WS_{start}-WS_{end}}\times 100 \\
AU_{(px)}= MAX_{(px)}\ -\ \frac{MAX_{(px)}-MIN_{(px)}}{WS_{start}-WS_{end}}\times WS_{start}
```
:::note warn
ただし、$AU_{(px)}$の与式における
$( WS_{start}, MAX_{(px)} )$ は $( WS_{end} , MIN_{(px)} )$ に置換可能
:::

```math
VAL_{(vw)}=RU_{(vw)} + AU_{(px)}
```
また$VAL_{(vw)}$について上記の式が成り立つため、$VAL_{(vw)}$を$( WS_{start}, MAX_{(px)} )$  $( WS_{end} , MIN_{(px)} )$ を用いて表すことが出来ました。

単純な一次関数との違いは$RU_{(vw)}$の算出のために100をかける必要がある点です。vwの取る値は百分率です。そのため割合のままではVALの値を正しく計算できません。一方で$AU_{(px)}$の計算はこれまでの一次関数の計算と同様に割合を用います。そのため$AU_{(px)}$の計算に$RU_{(vw)}$がそのまま流用できない点に注意してください。

### 3-3-5.VALの値内の切片をpxからremに置き換える
ここまで$VAL_{(vw)}$の切片$AU_{(px)}$はpxと仮定して進めてきましたが、フォントサイズの単位はpxではなくremを使用しているケースもよくあります。そのため、事前に変換係数$\alpha$を設定しておくことで、変換係数をフォントサイズ(px)にかければフォントサイズ(rem)が得られるようにしておきます。

:::note
remの基準となるルートのフォントサイズを $root_{(px)}$ とする
:::

```math
\alpha = \frac{1}{root_{(px)}}
```

# 4.clamp関数のscss関数化 
> VALの値の決定に感覚や主観が介入することは制作効率の悪化にも繋がります。

先にこのように述べた通り、clamp関数を実用化したい場合scssを用いて独自関数化することをおすすめします。

clamp関数を使うたびにツールを用いてVALの値を計算するのでは制作効率の低下は避けられません。その点独自関数は任意の引数を設定するだけで自動的にVALの値を計算してくれるため、一貫性と制作効率の両側面から見て優れています。

## 4-1.clamp関数の既存の独自関数
今回は[こちら](https://qiita.com/soundweaver/items/9b5a3a7c0b0e198f4570)の記事で紹介されている独自関数を基に、様々な状況設定でも汎用的に使うことのできるロバストな独自関数を目指します。

## 4-2.既存の関数から拡張可能な点
既存の関数は引数を4つ持っていますが、毎回引数に同じ値を持たせる場合では記述を省略することが出来ます。

### 4-2-1.MINのデフォルト値
「MINの値の決定方法」の節でも言及した通り、MINの値はある程度の選択基準がないとジャンプ率で破綻を起こす可能性があります。タブレット系統やスマートフォン系統のデザインカンプの値を引数に指定する場合は問題ありませんが、そうでない場合のためにデフォルト値として選択基準を基に計算した値を設定し、任意の値を取りたい場合は引数で指定するのが良いと思います。

### 4-2-2.ウィンドウサイズの上限下限値の決定方法
VALの値の決定方法には以下の2種類がありました。

**1.** $WS_{start} = BP_{max}$ かつ $WS_{end} = BP_{min}$の場合
**2.** $WS_{start} \neq BP_{max}$ または $WS_{end} \neq BP_{min}$の場合

2つのシチェーションのうち、$WS_{start} = BP_{max}$ かつ $WS_{end} = BP_{min}$ の場合では $WS_{start}$と$WS_{end}$ において任意の値を取らないため、1つのプロジェクトで同じ値が繰り返し用いられるケースが多いと考えられます。

そのためデフォルト値としてそれぞれ $WS_{start} = BP_{max}\ ,$  $WS_{end} = BP_{min}$ を設定し、任意のウィンドウサイズ間でレスポンシブしたい場合は、引数で設定するのが良いと思います。

### 4-2-3.rem変換に関する拡張
既存の関数ではremの $root_{(px)}=16$ で固定されていますが、変換係数を用いることで $root_{(px)} \neq 16$ であるプロジェクトに対しても同様の独自関数が使えるようにします。またVALの切片の単位をremで計算するかpxで計算するかを指定するフラグも用意します。

## 4-3.拡張後の独自関数
前述の通り[こちら](https://qiita.com/soundweaver/items/9b5a3a7c0b0e198f4570)で紹介されている独自関数を参考に拡張しています。
$WS_{start}$と$WS_{end}$のデフォルト値にはよくメディアクエリで用いられるウィンドウサイズの変数を流用しています。またMINのデフォルト値はMAXの値の80%としています。

この独自関数はpx基準で作られています。そのためremで使用すると変換係数の四捨五入の影響で誤差の範囲でズレが生じます。主にremで使用したい場合は変換係数の代わりに$root_{px}$をremの値にかけてrem基準にしてしまっても良いと思います。


```scss:mixin.scss
// コンテンツ幅
$sm: 600;
$md: 768;
$lg: 1200;
$xl: 1400;
$xx: 1920;
```
```scss:function.scss
@use "sass:math";
@use "mixin";

// 小数点込みで四捨五入する（$floatIndex:2 のとき小数第2位までの数値で出力）
@function divFunc($value01,$value02,$floatIndex:2){
    $floatNum: math.pow(10,$floatIndex);
    $divValue: math.div($value01,$value02)*$floatNum;
    $roundValue: math.div(math.round($divValue),$floatNum);
    @return $roundValue;
}
// clamp計算
@function clampAuto($fontMax,$fontMin:($fontMax * 0.8),$windowMax:mixin.$xl,$windowMin:mixin.$md,$remBoolean:false,$remRoot:16) {
    $vwRootRate: 1px;
    @if $remBoolean == true{
        $remRootRate: divFunc(1rem,$remRoot);
    }
    $fontRate: $fontMax - $fontMin;
    $windowRate: $windowMax - $windowMin;

    $valSlope: divFunc($fontRate,$windowRate,$floatIndex:4) * 100vw;
    $valIntercept: (($fontMax - divFunc($fontRate,$windowRate,$floatIndex:4) * $windowMax) * $remRootRate);

    $fontMaxReturn: $fontMax * $remRootRate;
    $fontMinReturn: $fontMin * $remRootRate;

    @return clamp(#{$fontMinReturn}, #{$valIntercept} + #{$valSlope}, #{$fontMaxReturn});
}
```

```scss:sample.scss
.sample01{
    font-size: function.clampAuto($fontMax:40);
}
// font-size: clamp(32px, 17.8px + 1.85vw, 40px);

.sample02{
    font-size: function.clampAuto($fontMax:32,$fontMin:24);
}
// font-size: clamp(24px, 9.8px + 1.85vw, 32px);

.sample03{
    font-size: function.clampAuto($fontMax:36,$fontMin:26,$windowMax:1200,$remBoolean:true);
}
// font-size: clamp(1.56rem, 0.4968rem + 2.31vw, 2.16rem);

.sample04{
    font-size: function.clampAuto($fontMax:36,$fontMin:26,$windowMax:1200,$windowMin:1000,$remBoolean:true,$remRoot:10);
}
// font-size: clamp(2.6rem, -2.4rem + 5vw, 3.6rem);
```

# 5.まとめ
clamp関数はリキッドデザインや部分的なレスポンシブで有効なcss関数でした。また面倒なVAL計算も関数化することで省略することができました。

デバイスの多様化によりWeb制作におけるリキッドデザインの需要は以前よりも増しています。それに伴いclamp関数が必要になる機会も今まで以上に増えることが予想されます。その際にこの記事が少しでもお役に立てれば嬉しく思います。

○ 参考記事
https://developer.mozilla.org/ja/docs/Web/CSS/clamp
https://developer.mozilla.org/ja/docs/Web/CSS/min
https://developer.mozilla.org/ja/docs/Web/CSS/max
https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Functions
https://www.m-hand.co.jp/design/8504/
https://qiita.com/soundweaver/items/9b5a3a7c0b0e198f4570