Sass と clamp() で作る可変のフォントサイズ
任意の 4 つの値で作成
最近このようなフォントサイズの指定をよく見かけます。
h1 {
font-size: clamp(1.5rem, -0.6094rem + 5.625vw, 3.75rem);
}
この clamp()
は、以下の動作をします。
- ビューポートが 37.5rem(600px)以下では、フォントサイズは 1.5rem(24px)になる。
- ビューポートが 77.5rem(1240px)以上では、フォントサイズは 3.75rem(60px)になる。
- ビューポートが 37.5rem と 77.5rem の間は、フォントサイズはビューポートに比例し 1.5rem から 3.75rem の間で変わる。
実際に指定したのが、下の文字です。
ブラウザのウィンドウ幅の変更に応じて、文字の大きさが変わります。
このフォントサイズの指定は、CSS-Tricks が Linearly Scale font-size with CSS clamp() Based on the Viewport で紹介しています。「最小のフォントサイズ」「最大のフォントサイズ」「最小のフォントサイズになるビューポート」「最大のフォントサイズになるビューポート」の 4 つの任意の値から clamp()
を作るものです。また、ジェネレーターが用意されています。
ありがたいことに計算式も書かれているため、そのまま Sass で独自関数を作れます。引数の検証をしない簡易なものですが、このような関数を作ってみます。
@use 'sass:map';
@use "sass:math";
$base-font-size: 16;
// ビューポートはここで指定する
$breakpoints: (
'small': 600,
'large': 1240
);
@function strip-unit($num) {
@return math.div($num, ($num * 0 + 1));
}
@function divide-root($px) {
@return math.div(strip-unit($px), $base-font-size);
}
@function rem($px) {
@return divide-root($px) * 1rem;
}
// 参考:https://css-tricks.com/linearly-scale-font-size-with-css-clamp-based-on-the-viewport/
@function fluid-size($min-size, $max-size) {
$min-unit: rem($min-size);
$max-unit: rem($max-size);
$min-num: divide-root($min-size);
$max-num: divide-root($max-size);
$min-breakpoint: divide-root(map.get($breakpoints, 'small'));
$max-breakpoint: divide-root(map.get($breakpoints, 'large'));
$slope: math.div(($max-num - $min-num), ($max-breakpoint - $min-breakpoint));
$calc-rem: ($min-breakpoint * -1 * $slope + $min-num) * 1rem;
$calc-vw: ($slope * 100) * 1vw;
@return clamp($min-unit, $calc-rem + $calc-vw, $max-unit);
}
使いたい場所で fluid-size()
を使います。第 1 引数は最小のフォントサイズ、第 2 引数は最大のフォントサイズを px で指定します。
h1 {
font-size: fluid-size(24px, 60px);
// 引数に単位があるかの確認はしていないため単位なしでも構わない
//font-size: fluid-size(24, 60);
}
コンパイルを行えば、CSS-Tricks のジェネレーターとほぼ同じ(小数点以下の桁数が違う)clamp()
を生成します。
h1 {
font-size: clamp(1.5rem, -0.609375rem + 5.625vw, 3.75rem);
}
サイトのデザインガイドラインによりますが、font-size
に限らず余白にも使えると思います。
欠点
ただし、この clamp()
には欠点があります。それは CSS-Tricks の記事の中で書かれています。
We are converting the widths, 360px and 840px, to rem units by dividing them by 16 because that’s what we assume is the root’s font size. If the user has their preferences set to another root font size, say 18px instead of the default 16px, then that calculation is going to be wrong and the text won’t resize the way we’d expect.
意訳:ルートのフォントサイズを 16px と想定し、ビューポートの 360px と 840px を 16 で割って単位を rem に変換しています。 ユーザーが別のルートフォントサイズ、たとえばデフォルトの 16px ではなく 18px に設定している場合は計算は間違っており、テキストのサイズは期待どおりに変更されません。
ブラウザの設定からフォントサイズを変更した場合は、clamp()
の推奨値(真ん中の式の部分)の計算結果が間違ってしまうというもの。例えば、ブラウザの設定のフォントサイズを 16px より大きくした場合、clamp()
の推奨値でのテキストのサイズは本来より小さくなります。
欠点を理解した上で、この clamp()
を使用するかを決める必要があります。あるいは、解決案として書かれている JavaScript でルートのフォントサイズを取得する方法を用いるのがいいかもしれません。
他の情報
CSS-Tricks は、2017 年に Fluid Typography で可変のフォントサイズの考えを紹介しています。また、2018 年には Fun Tip: Use calc() to Change the Height of a Hero Component で padding
などに使用する案も紹介しています。いずれも現在のジェネレーターとは式が異なりますが、結果は現在のジェネレーターと同じです(現在のジェネレーターは式が簡素になったため、より少ない CSS で済む)。
他に、Fluid-responsive font-size calculator も可変のフォントサイズを生成するジェネレーターです。式が CSS-Tricks のものと少し違いますが、結果は同じです。