この記事は「Snow Monkey / unitone Advent Calendar 2025」の12月10日の記事です。まだまだ枠が空きまくっているのでぜひご参加ください!参加賞もあります!
まずは文字サイズの実装について
文字サイズについてはこれまでと変わりがなく調和数列をベースとしたものになっています。詳細が気になる方は過去の記事をご参照ください。
line-height の実装
unitone の line-height 実装の基本的な考え方は下記のとおりです。
そして肝心の line-height の実装について。上記で、文字サイズは –unitone–font-size を指定して算出できることを示しましたが「文字サイズを指定するたびにその文字サイズに最適な line-height も指定するのは面倒なので、文字サイズを指定すればその文字サイズに最適な line-height も自動的に反映されるようにしたい」と考えました。
上記は昨年の記事からの引用ですが、開発当初から一貫してこの考えです。
昨年からの計算式は下記のとおり。
- 基本の
line-heightは1.8(line-heightの最大値)。 - 文字サイズが大きくなるほど
line-heightを小さくする。 - 文字サイズが一定を超えると緩さが目立つので、基本の文字サイズの5倍のサイズ(現状決め打ち)のときに
1.1(line-heightの最小値)となるようにする。 - (↑今はリニアな関数なので、指数関数的な関数にしたほうが良いのでは?感はあります。でも良いやり方がわからない。ご意見募集!)
これを式にするとこうなります。
{line-height} = -0.1 * {文字サイズの倍率(つまり何remか)} + 1.9;
※ただし line-height が 1.1 未満になったり、1.8 以上になることはない。
※また、文字サイズの倍率が5倍以上の場合は line-height は 1.1 を超えないものとする。
今年は昨年よりもう一歩踏み込んで、--unitone--font-size を指定せずに直接 style="font-size: 42px" のように指定した場合でも、最適な line-height が適用されるようにしました!(これまでも対応しているっちゃしていましたが、JavaScript が必要でした。今は不要!)
実際のコード
(わかりやすくするために実際とは異なる改変をしています)。
@property --unitone--property--1em {
syntax: "<length>";
inherits: false;
initial-value: 0;
}
* {
/**
* A value to get the unitless px value in trigonometric functions. (For Safari)
*/
--unitone--property--1em: 1em;
/**
* em unitless px real value
*/
--unitone--result--1em-px: calc(tan(atan2(var(--unitone--property--1em), 1px)));
}
:root {
/**
* The 1rem px font size (no units).
*/
--unitone--base-font-size: 16;
/**
* Gutter provided above and below the text in a line.
*/
--unitone--half-leading: .3;
--unitone--min-half-leading: .05;
--unitone--min-line-height: calc(1 + 2 * min(var(--unitone--min-half-leading), var(--unitone--half-leading)));
--unitone--max-line-height: calc(1 + 2 * var(--unitone--half-leading));
}
$_max-line-height-target-font-size-ratio: 5;
$_line-height-slope: calc((var(--unitone--min-line-height) - var(--unitone--max-line-height)) / #{ $_max-line-height-target-font-size-ratio - 1 });
$_line-height-intercept: calc(var(--unitone--max-line-height) - #{ $_line-height-slope });
$line-height: clamp(
var(--unitone--min-line-height),
#{ $_line-height-slope } * (var(--unitone--result--1em-px) / var(--unitone--base-font-size)) + #{ $_line-height-intercept },
var(--unitone--max-line-height)
);
昨年からアップデートしたこと
- 文字サイズの実数値を CSS で計算して、最適な
line-heightを適用 line-heightの最小値を CSS カスタムプロパティ化line-heightの最大値を CSS カスタムプロパティ化
解説
tan()、atan2() を使った「1em → px」の変換
今回の実装で最も分かりにくいのは「1em を px に変換するためになぜ三角関数を使うのか?」という点かなと思います。
結論から言えば、Safari が三角関数の内部で <length>(単位つきの値)をうまく扱えないため、em →px(単位なし)に変換する工夫が必要だからです。
CSS の tan() や atan2() は数値または角度を受け取ります。ところが Safari は、
tan(1em)
のような「単位付きの長さ」を入力するとうまく計算できない場合があります。そこで下記の「単位を外す」ためのトリックを使います。
1em(長さ)→ 角度 → 角度の傾き(単位なしの数値)
まず、atan2(y, x) は「(x, y) というベクトルの角度」を返します。たとえば
atan2(1em, 1px)
これは「底辺 1px、縦 1em の三角形の角度」を返します。その角度を tan() に渡すと角度の傾き(縦 / 底辺)がわかります。これは 1em / 1px なので「1em は 1px の何倍なのか」がわかります。
tan(atan2(1em, 1px)) = 1em / 1px
つまり 1em を px に変換した、単位のない実数値が得られます。
@property と CSS カスタムプロパティを使う理由
tan()・atan2() に直接単位付きの値を渡すと挙動が怪しい場合があるので、
@propertyで--unitone--property--1emを<length>に固定- それを使って
1emをpx(単位なし)に変換した値を CSS カスタムプロパティ(--unitone--result--1em-px)として保存
と、しています。
@property --unitone--property--1em {
syntax: "<length>";
inherits: false;
initial-value: 0;
}
--unitone--property--1em: 1em;
--unitone--result--1em-px: calc(tan(atan2(var(--unitone--property--1em), 1px)));
コメントを残す