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

Advertisements

Clamp()は、最小値(min)・推奨値(preferred)・最大値(max)の3つの値を設定して、
基本は推奨値を取りつつ、

  1. 推奨値が最小値より小きくなった場合は最小値
  2. 推奨値が最大値より大きくなった場合は最大値

となる、レスポンシブ対応に便利な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

となります。

Advertisements

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段階の遷移をするフォントサイズになっています。

  1. スマホ(375px)〜 タブレット(768px)で 16px → 20px
  2. タブレット(768px)〜 PC(1280px)で 20px → 24px
Advertisements

SCSSの応用③: 2つか3つの文字サイズからclamp()値

最後に、ここまで行くとちょっと複雑な気もしますが、先ほどの3つの文字サイズからclamp()を出す式を応用して、

  1. 2つ文字サイズがあった場合はSPとPCの文字設定とする
  2. 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()自体、基本的にどの主要ブラウザでも対応しているので、
スッキリさせつつ見やすいフォント設計に
役立つことがあれば幸いです!

Advertisements