狙った文字サイズで可変できるSCSS

これは中級CSS&SCSSコーダー向けの技術記事ですー。
サクッとコピペして使ってもいいよー。

何?

これまでもレスポンシブウェブサイトで文字サイズ可変をしていたのだけど、100vwに対する文字数で算出した値を使っていたので、1行あたり文字数でのコントロールはできてもピクセル単位での文字サイズはコントロールが出来てなかったのね。

そこで、ブレイクポイントごとに文字サイズをピクセル指定して、ブレイクポイント間はCSSで計算して補完させちゃおうというのを思いついたわけです。

なにはともあれ、デモをご覧くだされ。なお、便宜上ブレイクポイントを超えるごとに文字色が変わるようになっている。

事前に設定した5つのブレイクポイントと最小320pxに合わせて、500px, 200px, 150px, 50px, 20px, 5pxになるように変化を起こすんだけど、要素自体にあてるコードは

@include fs(500,200,150,50,20,5);

これだけで済むようにミックスインを開発した。

この記事はその解説となります。

前提知識

今回はremという単位を使用している。
remはルート…つまりhtml要素の文字サイズを基準とした相対単位で、たとえば、

/* 例1 */
html { font-size: 18px;}
h1 { font-size: 1rem;}   /* 18px */
h2 { font-size: 0.8rem;} /* 14.4px */
/* 例2 */
html { font-size: 10px;}
h1 { font-size: 1rem;}   /* 10px */
h2 { font-size: 0.8rem;} /* 8px */

このようになる。
emや%のように親要素の文字サイズに引っ張られることはないし、px指定のようにガッチガチにもならない。

さて、htmlにあてられているブラウザデフォルト文字サイズが16pxと言われているので、何も指定しなければほとんどのブラウザで1rem=16pxになるのだけど、じゃあ例えば「20pxにしたい」となったときに何remかを計算するのは面倒。なのでこういうことをする。

html { font-size: 62.5%;}
h1 { font-size: 2.0rem;}   /* 20px */
h2 { font-size: 0.8rem;} /* 8px */

あらかじめhtml要素を62.5%…つまり16pxの62.5%である10pxを基準にしてあげれば、remの計算も楽になるというもの。

calc()

calc()というのはcssで使える四則演算用のファンクションみたいなもので、例えば親要素の50%+300pxにしたいとなればこのようになる。

div { width: calc(50% + 300px); }

閲覧者のその時の画面幅なんてものはわからないので、このように計算式をダイレクトに放り込むことで、ブラウザ状況に合わせた描写をさせることが出来る。

このcalc()は数値を扱うプロパティのすべてで使うことが出来るのでマジで便利。

一定の範囲で文字サイズを可変させる計算式

font-size: calc(ZZ + ((1vw - XX) * YY))

XX、YY、ZZは次の通り、

XX = ビューポートの最小幅 / 100 のrem変換値
YY = 100 * 文字サイズ可変量 / ビューポート可変量
ZZ = 最小文字サイズのrem変換値

例を挙げると、640~1200pxの間で24~36pxの文字サイズに可変させるには

XX = 640 / 100 * 0.1rem
YY = 100 * (36 - 24) / (1200 - 640)
ZZ = 24 * 0.1rem

つまり

font-size: calc(2.4rem + ((1vw - 0.64rem) * 2.1429));

こうなる。

この例は、ペコプラさんから引用させていただいた。
https://pecopla.net/web-column/fontsize-css

さて、ペコプラさんもおっしゃっているが、このcalcでの計算をさせるための計算自体がものすごく面倒なので、カリキュレーターというものもちゃんとある。
https://websemantics.uk/tools/responsive-font-calculator/

このようにA地点とB地点2か所で制御を行いたい場合はこれで計算してcssに放り込んであげればいいのだけど、例えばこれがビューポート5段階でやろうと思うとものすごく手間になる。

これをSCSSで簡単にやってやろうというのが今回のコード。

便利なファンクションとミックスイン

これを実現するためにはさらにいくつかのミックスインを使う。

まず、ピクセル数値指定した値をremに変換するファンクション。

@function rem($px,$base: 10) {
 @return $px / $base * 1rem;
}
SCSS: font-size: rem(18)
↓
CSS:  font-size: 1.8rem

こんな感じで使える。こんなん手作業でやっちゃえよとも思うけど、ファンクションの中には都度手入れなどできないので使ってしまう。

次に、単位を除去するファンクション。

@function strip-unit($number) {
 @if type-of($number) == 'number' and not unitless($number) {
   @return $number / ($number * 0 + 1);
 }
 @return $number;
}

これは単位付きの引数で四則演算するために必要になる。

そして、超便利なブレイクポイントの管理とメディアクエリーの出力ミックスイン。

$breakpointList : 399px, 599px, 823px, 979px, 1199px;

$breakpoints : (
 xs:"only screen and (max-width: #{nth($breakpointList,1)})",
 xs-s:"only screen and (min-width: #{nth($breakpointList,1)+1}) and (max-width: #{nth($breakpointList,2)})",
 s:"only screen and (max-width: #{nth($breakpointList,2)})",
 s-m:"only screen and (min-width: #{nth($breakpointList,2)+1}) and (max-width: #{nth($breakpointList,3)})",
 m:"only screen and (max-width: #{nth($breakpointList,3)})",
 mmin:"only screen and (min-width: #{nth($breakpointList,3)})",
 m-l:"only screen and (min-width: #{nth($breakpointList,3)+1}) and (max-width: #{nth($breakpointList,4)})",
 l:"only screen and (max-width: #{nth($breakpointList,4)})",
 lmin:"only screen and (min-width: #{nth($breakpointList,4)})",
 l-ul:"only screen and (min-width: #{nth($breakpointList,4)+1}) and (max-width: #{nth($breakpointList,5)})",
 ul:"only screen and (max-width: #{nth($breakpointList,5)})",
 d:"only screen and (min-width: #{nth($breakpointList,5)+1})",
);
@mixin media($breakpoint) {
 @media #{map-get($breakpoints, $breakpoint)} {
  @content;
 }
}

$breakpointListはブレイクポイント5点を配列管理
$breakpointsでメディアクエリーの出力内容のマップ
@mixin media($breakpoint)で呼び出し用のミックスイン

例えば、次のように変化点を指定するだけで展開されるようになるし、あとからブレイクポイント値を変えても全部追従してくれる。

/* SCSS */
h1 {
 color: black;
 @include media(ul) {
   color: red;
 }
}

/* CSS */
h1 {
 color: black;
}
@media only screen and (max-width: 1199px) {
 h1 {
   color: red;
 }
}

やっと本題

上のファンクションやミックスインを搭載しているのを前提として…

まず、文字サイズ可変計算をするファンクション部分

@function fscalc($fsfrom,$fsto,$vpfrom,$vpto) {
 $viewport_Width_Minimum : $vpfrom;
 $font_Size_Difference : $fsto - $fsfrom;
 $viewport_Width_Difference : strip-unit($vpto) - strip-unit($vpfrom);

 $XX : strip-unit($viewport_Width_Minimum) / 100 * 0.1rem;
 $YY : 100 * $font_Size_Difference / $viewport_Width_Difference;
 $ZZ : $fsfrom / 10 + 0rem;

 @return calc(#{$ZZ} + ((1vw - #{$XX}) * (#{$YY})));
}

引数を先ほどの計算式に当て込んだだけだけど、calc()の中で変数を使うときはインターポーレーション#{}にしないと動作しないので注意。
$fsfrom:最小文字サイズ
$fsto:最大文字サイズ
$vpfrom:最小ビューポート
$vpto:最大ビューポート
以上4点を投げ込めば計算後のcalc()が返ってくる仕組み。

次にブレイクポイントごと+最小画面幅の6点を可変計算式に投げ込むミックスイン

@mixin fs($ul,$l,$m,$s,$xs,$min:10) {
 & {
  @include media(d){& {font-size: rem($ul);}}
  @include media(l-ul){font-size: fscalc($l,$ul,nth($breakpointList,4)+1,nth($breakpointList,5))}
  @include media(m-l){font-size: fscalc($m,$l,nth($breakpointList,3)+1,nth($breakpointList,4))}
  @include media(s-m){font-size: fscalc($s,$m,nth($breakpointList,2)+1,nth($breakpointList,3))}
  @include media(xs-s){font-size: fscalc($xs,$s,nth($breakpointList,1)+1,nth($breakpointList,2))}
  @include media(xs){font-size: fscalc($min,$xs,320,nth($breakpointList,1))}
 }
}

メディアクエリー出力を出力するミックスインの中で、計算式に投げ込むファンクションを発動している。ブレイクポイントの位置を変えても計算式に突っ込まれるビューポート値もそれを追ってくれるので、手作業での再計算は必要ない。

わたしの場合は$breakpointListがpx付きになっているため、fscalc()の中の$vpfrom,$vptoで演算エラーを起こすので、ファンクションの中でstrip-unit($vpto)やstrip-unit($vpfrom)のようにさらに単位除去のファンクションをかけている。

あとは冒頭の通りこのミックスインを呼び出すだけで、任意の文字サイズ可変を起こすことが出来る。

h1{ @include fs(500,200,150,50,20,5); }

↑が↓に展開される

@media only screen and (min-width: 1200px) {
 h1 {
   font-size: 50rem;
 }
}
@media only screen and (min-width: 980px) and (max-width: 1199px) {
 h1 {
   font-size: calc(20rem + ((1vw - 0.98rem) * (136.9863013699)));
 }
}
@media only screen and (min-width: 824px) and (max-width: 979px) {
 h1 {
   font-size: calc(15rem + ((1vw - 0.824rem) * (32.2580645161)));
 }
}
@media only screen and (min-width: 600px) and (max-width: 823px) {
 h1 {
   font-size: calc(5rem + ((1vw - 0.6rem) * (44.8430493274)));
 }
}
@media only screen and (min-width: 400px) and (max-width: 599px) {
 h1 {
   font-size: calc(2rem + ((1vw - 0.4rem) * (15.0753768844)));
 }
}
@media only screen and (max-width: 399px) {
 h1 {
   font-size: calc(0.5rem + ((1vw - 0.32rem) * (18.9873417722)));
 }
}

ここで出てきている「50rem」はhtml要素の文字サイズ62.5%を加味したサイズになるので、=500pxとなる。
20remは200px、0.98remは9.8px、画面サイズが1199pxなら1vwは11.99px。

ちょっと雑だけど200px + (11.99px - 9.8px) * 137…とすれば=500.07px
ぱーぺき(パーフェクト&完璧)!

それでも面倒

実際に使ってみると5点も6点も細かく制御する必要がない場面もあったので、さらにこんなものも追加してみた。

@mixin fs2($ul,$min) {
 $l  : ($ul - $min) * (4/5) + $min;
 $m  : ($ul - $min) * (3/5) + $min;
 $s  : ($ul - $min) * (2/5) + $min;
 $xs : ($ul - $min) * (1/5) + $min;
 @include fs($ul,$l,$m,$s,$xs,$min);
}

これはブレイクポイントの一番大きい時と最小画面幅320px時点での文字サイズの2点さえ指定すれば、等分割して中間の指定を作り出したうえで先のファンクションに放り込んでくれるお手軽ミックスイン。

p {
 @include fs2(20,10);
}

↑だけで↓に展開される

@media only screen and (min-width: 1200px) {
 p {
   font-size: 2rem;
 }
}
@media only screen and (min-width: 980px) and (max-width: 1199px) {
 p {
   font-size: calc(1.8rem + ((1vw - 0.98rem) * (0.9132420091)));
 }
}
@media only screen and (min-width: 824px) and (max-width: 979px) {
 p {
   font-size: calc(1.6rem + ((1vw - 0.824rem) * (1.2903225806)));
 }
}
@media only screen and (min-width: 600px) and (max-width: 823px) {
 p {
   font-size: calc(1.4rem + ((1vw - 0.6rem) * (0.8968609865)));
 }
}
@media only screen and (min-width: 400px) and (max-width: 599px) {
 p {
   font-size: calc(1.2rem + ((1vw - 0.4rem) * (1.0050251256)));
 }
}
@media only screen and (max-width: 399px) {
 p {
   font-size: calc(1rem + ((1vw - 0.32rem) * (2.5316455696)));
 }
}

実例

Vtuber絡みのイベントで製作したサイトに実装をしたので、画面サイズをぐりぐりして試してみてほしい。

https://nmnm.mn/indiVGT/result2019/rule

まだまだ調整は必要だけど、このサイトはすべての文字サイズにこのミックスインを使っている。さらに発展形としては省庁系や自治体系にありがちな「文字サイズ:大中小」のような機能も、それでhtml属性の文字サイズ値を変化させてあげればすべてが追従するようになる。

ウェブサイトはユーザビリティをどんどん重視するよう動いてきているが、それに応じるコーダー側は逆に負担が大きくなってしまう。SCSSを活用して少しでも怠けていこう。

この記事が気に入ったらサポートをしてみませんか?