CSS カスタムプロパティによる流動的フォントサイズ
はじめに
画面幅に対して流動的にフォントサイズを変化させたい場合、vw
などの単位を用いると実現できます。たとえば font-size: 5vw;
を指定すると、幅 960px
ではフォントサイズは 48px
、幅 320px
では 16px
になります。しかし、320px
で 16px
は小さすぎるので 32px
にしたいとなると、計算が必要になってきます。
この計算方法や流動的フォントサイズのさらなる詳細については、以前登壇したときの資料をご覧いただきたい 👇
資料にもございますが、計算して CSS を出力してくれるアプリも作っています 👇
Sass の mixin もあります 👇
今回は、calc()
clamp()
min()
max()
と CSS カスタムプロパティを駆使して、自分で計算する必要がなく、Sass なども不要で CSS のみでの実装を紹介いたします。
使い方と実装解説
デモとソースコード
使い方
まずは下記コードを CSS に貼り付けてください。コメント部分はドキュメントなので任意ですが、使い方の参考になります。あとはコメント部分のドキュメントを参考し、任意の要素に必要な CSS カスタムプロパティを指定するだけです。
/**
* Available vars:
* @var --viewport-from: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --viewport-to: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --font-size-from: <number> - Number in pixels without the unit. Required if `--font-size` and `--min-font-size` is not exist.
* @var --font-size-to: <number> - Number in pixels without the unit. Required if `--font-size` and `--max-font-size` is not exist.
* @var --max-font-size: <number> - Number in pixels without the unit. Optional.
* @var --min-font-size: <number> - Number in pixels without the unit. Optional.
* @var --viewport-unit-converter: 1vw | 1vh | 1vmin | 1vmax - Optional. Default: 1vw.
* @var --font-size: <length> | <percentage> | <absolute-size> | <relative-size> | Global values - Optional.
*/
*,
*::before,
*::after {
--viewport-unit-converter: 1vw;
--fz-from: var(--font-size-from, var(--min-font-size));
--fz-to: var(--font-size-to, var(--max-font-size));
--fz-slope: (var(--fz-to) - var(--fz-from)) / (var(--viewport-to) - var(--viewport-from)) * 100;
--fz-intercept: (var(--viewport-to) * var(--fz-from) - var(--viewport-from) * var(--fz-to)) / (var(--viewport-to) - var(--viewport-from));
--font-size: calc(var(--fz-slope) * var(--viewport-unit-converter) + var(--fz-intercept) * 1px);
--min-fz-px: calc(var(--min-font-size) * 1px);
--max-fz-px: calc(var(--max-font-size) * 1px);
--clamp: clamp(var(--min-fz-px), var(--font-size), var(--max-fz-px));
--max: var(--has-max, var(--min));
--min: var(--has-min, var(--font-size));
--has-max: min(var(--max-fz-px), var(--font-size));
--has-min: max(var(--min-fz-px), var(--font-size));
font-size: var(--clamp, var(--max));
}
例 1:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
この場合、幅 320px
では 32px
、960px
では 48px
になりますが、320px
以下や 960px
以上でも同じ変化率で小さくなったり大きくなったりします。
例 2:
h1 {
--viewport-from: 320;
--viewport-to: 960;
--min-font-size: 32;
--max-font-size: 48;
}
この場合は上記と同じ変化率ですが、32px
以下や 48px
以上にはならない。
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
--min-font-size: 32;
--max-font-size: 48;
}
このように記述しても同じ結果になります。--font-size-from
と --min-font-size
、--font-size-to
と --max-font-size
がそれぞれ同じ場合は、--font-size-from
と --font-size-to
は省略できます。
例 3:
h1 {
--viewport-from: 320;
--viewport-to: 960;
--min-font-size: 32;
--font-size-to: 48;
}
このように最小だけ、最大だけを指定することもできます。
例 4:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
--min-font-size: 36;
--max-font-size: 40;
}
--font-size-from
と --min-font-size
、--font-size-to
と --max-font-size
がそれぞれ違う値でも大丈です。この場合幅 320px
では 32px
、960px
では 48px
になるように変化させるが、その途中の 36px
と 40px
で変化は止まります。
例 5:
h1 {
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
@media (min-width: 960px) {
h1 {
--viewport-from: 960;
--font-size-from: 48;
--viewport-to: 1920;
--font-size-to: 64;
}
}
メディアクエリを使えば途中で変化率を変えられます。上記の例はメディアクエリ部分がなければ 1920px
では 72px
になるはずですが、960px
から変化率変えて 1920px
では 64px
になります。
例 6:
h1 {
--viewport-from: 320;
--font-size-from: 48;
--viewport-to: 960;
--font-size-to: 32;
--min-font-size: 16;
}
逆に大きいサイズから小さいサイズに変化させることもできます。最大最小値とも組み合わせられます。
例 7:
h1 {
--font-size: 2rem;
}
固定値も指定できます。--font-size
が指定してあれば、その他プロパティは不要です。px
以外の font-size
で指定できる値もすべて指定できます。
例 8:
h1 {
--font-size: calc(2.5vw + 24px);
}
独自の計算式を指定することもできます。
例 9:
h1 {
--font-size: calc(2.5vw + 24px);
--min-font-size: 32;
--max-font-size: 48;
}
独自計算式に最大最小値を設定することもできます。
例 10:
h1 {
--viewport-unit-converter: 1vh;
--viewport-from: 320;
--font-size-from: 32;
--viewport-to: 960;
--font-size-to: 48;
}
デフォルトでは vw
を用いて画面幅に対して計算しますが、vh
vmin
vmax
にもできます。注意として 1vh
のように 1
をつける必要があります。
Tailwind CSS での使い方
Tailwind CSS の @tailwind base
には Preflight (CSS ノーマライズ・リセットみたいなもの) が含まれています (tailwind.config.js
で無効化できます) 。その中には h1, h2, h3, h4, h5, h6
の font-size
に関する記述があるため、CSS ファイルには以下のように記述する必要があります。
@tailwind base;
@tailwind components;
@tailwind utilities;
/**
* Available vars:
* @var --viewport-from: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --viewport-to: <number> - Number in pixels without the unit. Required if `--font-size` is not exist.
* @var --font-size-from: <number> - Number in pixels without the unit. Required if `--font-size` and `--min-font-size` is not exist.
* @var --font-size-to: <number> - Number in pixels without the unit. Required if `--font-size` and `--max-font-size` is not exist.
* @var --max-font-size: <number> - Number in pixels without the unit. Optional.
* @var --min-font-size: <number> - Number in pixels without the unit. Optional.
* @var --viewport-unit-converter: 1vw | 1vh | 1vmin | 1vmax - Optional. Default: 1vw.
* @var --font-size: <length> | <percentage> | <absolute-size> | <relative-size> | Global values - Optional.
*/
*,
*::before,
*::after,
h1,
h2,
h3,
h4,
h5,
h6 {
--viewport-unit-converter: 1vw;
--fz-from: var(--font-size-from, var(--min-font-size));
--fz-to: var(--font-size-to, var(--max-font-size));
--fz-slope: (var(--fz-to) - var(--fz-from)) / (var(--viewport-to) - var(--viewport-from)) * 100;
--fz-intercept: (var(--viewport-to) * var(--fz-from) - var(--viewport-from) * var(--fz-to)) / (var(--viewport-to) - var(--viewport-from));
--font-size: calc(var(--fz-slope) * var(--viewport-unit-converter) + var(--fz-intercept) * 1px);
--min-fz-px: calc(var(--min-font-size) * 1px);
--max-fz-px: calc(var(--max-font-size) * 1px);
--clamp: clamp(var(--min-fz-px), var(--font-size), var(--max-fz-px));
--max: var(--has-max, var(--min));
--min: var(--has-min, var(--font-size));
--has-max: min(var(--max-fz-px), var(--font-size));
--has-min: max(var(--min-fz-px), var(--font-size));
font-size: var(--clamp, var(--max));
}
あとは arbitrary values (「任意値」、これまでは JIT モードとして知られている) でカスタムプロパティを指定するだけです。上記の「例 2」は以下のように記述します。
<h1 class="[--viewport-from:320] [--viewport-to:960] [--min-font-size:32] [--max-font-size:48]">
...
</h1>
以上で使い方は大体網羅できたかと思います。
解説
計算式はこれまで作ってきたものと同じなので割愛いたします。
ポイント 1
CSS カスタムプロパティの初期値や計算式を :root
にしてしまうと、値の継承で期待した結果にならないため、*
を使ってすべての要素に適用させています。
ポイント 2
CSS の calc()
は単位がついた値の加算減算はできますが、乗算除算はできません。なので単位がついていない値で計算したあとに、単位をつけてあげる必要があります。そのため、ほとんどのプロパティは単位なしの数値で指定する必要があります。
単位をつける方法は var(--fz-intercept) * 1px
のように単位のついた 1
を掛けることで実現できます。例 10 で 1vh
にしないといけないのはこのためです。
ポイント 3
最大最小値のあるなしの条件分岐については、clamp()
min()
max()
は正しく引数を渡さないと、その全体が正しくない値になり、それを CSS カスタムプロパティのフォールバックと組み合わせると、正しくない場合はフォールバックを参照することになり、それをバケツリレー形式でつなげて実現しています。
ソースコードや以前のツイートもご参考ください。
アクセシビリティ
こちらは px
を基に計算していますが、rem
に対応させることもできなくはないです。ただ、そうなると計算式がもっと複雑になり、さらに、ブラウザのデフォルトフォントサイズを変更している場合、期待した数値にはならなくなります。では逆にアクセシビリティ的にどうなの?ということですが、これは前々から主張していることですが、px
でコーディングされていても、ブラウザ側が文字サイズのみの拡大縮小を可能にするべきです。実際、Firefox と Safari はそれが可能です。もちろん今回の実装にも問題なく対応しています。
おわりに
別の研究で生まれた副産物ですが、意外と結構使えるものができたと思います。それではよい CSS ライフを。
Discussion