フォントを1行でレスポンシブ化するclamp()を、SCSSで設定する方法

Clamp()は、最小値(min)・推奨値(preferred)・最大値(max)の3つの値を設定して、
基本は推奨値を取りつつ、
- 推奨値が最小値より小きくなった場合は最小値
- 推奨値が最大値より大きくなった場合は最大値
となる、レスポンシブ対応に便利なCSSの関数です。
font-size: clamp(min, preferred, max);
今回は、ジェネレータや自力で計算する以外に、
SCSSで計算式を用意して、
フォントの最小値と最大値を入れるだけで
clamp()の式を出す仕組みをつくりたいと思います。
Clamp()の計算方法とSCSS
早速ですが、なめらかに移行するclamp()の計算式を、
vw、remの単位で表したscssの式はこちらになります。
@use "sass:math";
$min-width: 375px;
$max-width: 1280px;
// strip unit
@function strip-unit($value) {
@return math.div($value, ($value * 0 + 1));
}
// px → rem
@function rem($px) {
@return math.div(strip-unit($px), 16);
}
@mixin fluid-font($min-px, $max-px) {
$min-px-value: strip-unit($min-px);
$max-px-value: strip-unit($max-px);
$min-vp: strip-unit($min-width);
$max-vp: strip-unit($max-width);
// slope: px / vw
$slope: math.div(($max-px-value - $min-px-value), ($max-vp - $min-vp)) * 100;
// intercept: px
$intercept-px: $min-px-value - math.div($slope * $min-vp, 100);
font-size: clamp(#{rem($min-px-value)}rem, calc(#{rem($intercept-px)}rem + #{$slope}vw), #{rem($max-px-value)}rem);
}
math.div($a, $b) は割り算のことです。割り算の「 / 」は廃止予定なので、代わりにmath.divを使うためモジュールを読み込んで使います。
@use "sass:math"; /* 最初にモジュールを読み込んで使う */
また、strip-unit()で計算前に単位を取るため、呼び出し時点でpx単位のあり・なし両方とも動作します。
使用例としては、例えばcssで
h3 {
@include fluid-font(16px, 24px); /* strip-unitをするので、単位なしもOK */
}
と入れると、次のような出力になります。
h3 {
font-size: clamp(1rem, 0.7928176796rem + 0.8839779006vw, 1.5rem);
}
SCSSの推奨値の式の解説
先ほどのscssの式について、下記の推奨値の箇所について補足したいと思います。
// slope: px / vw
$slope: math.div(($max-px-value - $min-px-value), ($max-vp - $min-vp)) * 100;
// intercept: px
$intercept-px: $min-px-value - math.div($slope * $min-vp, 100);
font-size: clamp(#{rem($min-px-value)}rem, calc(#{rem($intercept-px)}rem + #{$slope}vw), #{rem($max-px-value)}rem);
}
clamp()の計算式の詳細については、
CSSのclamp()関数と、フォントサイズの設定方法で書きました。
上記の内容から、「min-vp から max-vp の間で min-size から max-size に滑らかに変化させる」clamp()の基本式はこのように表せます。
clamp(min-size, min-size + ((max-size - min-size) / (max-vp - min-vp)) * (current-vp - min-vp), max-size)
current-viewpoint=100vwなので、clamp()の真ん中の値、推奨値(preferred)は
min-size+((max-size – min-size) / (max-vp – min-vp)) * (100vw – min-vp) で表現できます。
$slope = ($max-size – $min-size) / ($max-vp – $min-vp) * 100;とすると、
preferred は $slope * 1 vw + ($min-size – $slope * $min-vp / 100) になります。
さらに、
$intercept = $min-size – ($slope * $min-vp / 100) とすると、
preferred は $slope * 1 vw + $interceptで表せます。
計算をしやすくするため、$slope と $intercept はpxベースで計算しています。
ここまで計算した時点で、$slope : 単位なし、$intercept : pxの単位になるため、
最後、rem()で$interceptのpxをremに変換して、
preferred は $slope vw + rem ($intercept) rem
となります。
SCSSの応用①: 設定済みのBreakpointsからclamp()値
次に、さきほどのscssをベースにして、$min-width と $max-width を直接数値ではなく、
すでに設定済みのBreakpoints から取得できるように SCSS をアレンジしてみます。
@use "sass:math";
$breakpoints: (
sm: 375px,
md: 768px,
lg: 1280px
);
// strip unit
@function strip-unit($value) {
@return math.div($value, ($value * 0 + 1));
}
// px → rem
@function rem($px) {
@return math.div(strip-unit($px), 16);
}
// get breakpoint value
@function get-breakpoint($name) {
@if map-has-key($breakpoints, $name) {
@return map-get($breakpoints, $name);
} @else {
@error "no breakpoint value #{$name}";
}
}
// fluid font-size
@mixin fluid-font($min-px, $max-px, $min-breakpoint: sm, $max-breakpoint: xl) {
$min-px-value: strip-unit($min-px);
$max-px-value: strip-unit($max-px);
$min-vp: strip-unit(get-breakpoint($min-breakpoint));
$max-vp: strip-unit(get-breakpoint($max-breakpoint));
// slope: px / vw
$slope: math.div(($max-px-value - $min-px-value), ($max-vp - $min-vp)) * 100;
// intercept: px
$intercept-px: $min-px-value - math.div($slope * $min-vp, 100);
font-size: clamp(
#{rem($min-px-value)}rem,
calc(#{rem($intercept-px)}rem + #{$slope}vw),
#{rem($max-px-value)}rem);
}
このようにすると、デフォルトではsmとlgの幅を使って計算、また他の幅を指定することも可能なcssを出すことができます。
/*使用例*/
h1 {
@include fluid-font(20px, 32px); // sm → lg を基準に計算
}
p {
@include fluid-font(14px, 18px, sm, md); // sm → md を基準に計算
}
SCSSの応用②: 3つの文字サイズからclamp()値
今までスマホ〜PCなど、2つの値の間を遷移するclamp()を出す式をご紹介しました。
さらに、タブレット時のフォントサイズも指定し、3つの値を一度に設定して滑らかに遷移するSCSSを考えてみます。
スマホ、PC、タブレットと、3つのフォントサイズを指定したい場合、clamp()単体では処理できないため、メディアクエリもつかって、2段階で書いていきます。
@use "sass:math";
$breakpoints: (
sm: 375px,
md: 768px,
lg: 1280px
);
// strip unit
@function strip-unit($value) {
@return math.div($value, ($value * 0 + 1));
}
// px → rem
@function rem($px) {
@return math.div(strip-unit($px), 16);
}
// get breakpoint value
@function get-breakpoint($name) {
@if map-has-key($breakpoints, $name) {
@return map-get($breakpoints, $name);
} @else {
@error "no breakpoint value #{$name}";
}
}
// fluid-font
@function fluid-font($min-px, $max-px, $min-breakpoint: sm, $max-breakpoint: lg) {
$min-px-value: strip-unit($min-px);
$max-px-value: strip-unit($max-px);
$min-vp: strip-unit(get-breakpoint($min-breakpoint));
$max-vp: strip-unit(get-breakpoint($max-breakpoint));
// slope: px / vw
$slope: math.div(($max-px-value - $min-px-value), ($max-vp - $min-vp)) * 100;
// intercept: px
$intercept-px: $min-px-value - math.div($slope * $min-vp, 100);
@return clamp(
#{rem($min-px-value)}rem,
calc(#{rem($intercept-px)}rem + #{$slope}vw),
#{rem($max-px-value)}rem
);
}
// fluid-font-2step
@mixin fluid-font-2step($sm-px, $md-px, $lg-px) {
// sm〜md
font-size: fluid-font($sm-px, $md-px, sm, md);
// md〜lg
@media screen and (min-width: #{get-breakpoint(md)}) {
font-size: fluid-font($md-px, $lg-px, md, lg);
}
}
このように設定すると、例えばCSSでよびだした場合
h3 {
@include fluid-font-2step(16px, 20px, 24px);
}
下記のように出力されます。
h3 {
font-size: clamp(1rem, 1.3333vw + 0px, 1.25rem);
}
@media screen and (min-width: 768px) {
h3 {
font-size: clamp(1.25rem, 1.25vw + 0.25rem, 1.5rem);
}
}
その場合、滑らかに下記のような2段階の遷移をするフォントサイズになっています。
- スマホ(375px)〜 タブレット(768px)で
16px → 20px
- タブレット(768px)〜 PC(1280px)で
20px → 24px
SCSSの応用③: 2つか3つの文字サイズからclamp()値
最後に、ここまで行くとちょっと複雑な気もしますが、先ほどの3つの文字サイズからclamp()を出す式を応用して、
- 2つ文字サイズがあった場合はSPとPCの文字設定とする
- 3つ文字サイズがあった場合は、SPとタブレット、PCの文字設定とする
となる式を条件分岐をしてつくってみました。
@use "sass:math";
$breakpoints: (
"sm": 375px,
"md": 768px,
"lg": 1024px,
"xl": 1280px,
);
// strip unit
@function strip-unit($value) {
@return math.div($value, ($value * 0 + 1));
}
// px → rem
@function rem($px) {
@return math.div(strip-unit($px), 16);
}
// get breakpoint value
@function get-breakpoint($name) {
@if map-has-key($breakpoints, $name) {
@return map-get($breakpoints, $name);
} @else {
@error "no breakpoint value #{$name}";
}
}
// fluid-font
@function fluid-font($min-px, $max-px, $min-breakpoint: sm, $max-breakpoint: xl) {
$min-px-value: strip-unit($min-px);
$max-px-value: strip-unit($max-px);
$min-vp: strip-unit(get-breakpoint($min-breakpoint));
$max-vp: strip-unit(get-breakpoint($max-breakpoint));
// slope: px / vw
$slope: math.div(($max-px-value - $min-px-value), ($max-vp - $min-vp)) * 100;
// intercept: px
$intercept-px: $min-px-value - math.div($slope * $min-vp, 100);
@return clamp(
#{rem($min-px-value)}rem,
calc(#{rem($intercept-px)}rem + #{$slope}vw),
#{rem($max-px-value)}rem
);
}
// fluid-font-2step(2 or 3 font size)
@mixin fluid-font-2step($args...) {
$length: length($args);
@if $length == 2 {
$sm-px: nth($args, 1);
$lg-px: nth($args, 2);
// sp〜pc
font-size: fluid-font($sm-px, $lg-px, sm, xl);
} @else if $length == 3 {
$sm-px: nth($args, 1);
$md-px: nth($args, 2);
$lg-px: nth($args, 3);
// sp〜tablet
font-size: fluid-font($sm-px, $md-px, sm, md);
// tablet〜pc
@media screen and (min-width: #{get-breakpoint(md)}) {
font-size: fluid-font($md-px, $lg-px, md, xl);
}
} @else {
@error "fluid-font-2step requires 2 or 3 arguments. Given: #{$length}";
}
}
このようにした場合、値を2つしか入れなくてもエラーにならず、また、3つ値をいれても動作します。
.title {
@include fluid-font-2step(14px, 20px); // sp〜pc
}
.subtitle {
@include fluid-font-2step(14px, 16px, 20px); // sp〜tablet〜pc
}
clamp()を、SCSSで設定する方法まとめ
以上、画面幅に応じた滑らかなフォントサイズになるclamp()を
SCSSで設定する方法についてご紹介しました。
基本的にはスマホ、PCの2サイズのみ設定しつつ、
不具合がある場合はタブレット時のサイズも調整する、
ということが多いのかな?と思いますが、
2つのフォントサイズの場合、3つのフォントサイズがある場合など、それぞれで書いてみました。
clamp()自体、基本的にどの主要ブラウザでも対応しているので、
スッキリさせつつ見やすいフォント設計に
役立つことがあれば幸いです!