【CSS】calc()とclamp()で作るフルードタイプスケール実装ガイド

下記がとても実践的で参考になったので、情報を補足しながら紹介します。

https://tobiasahlin.com/blog/responsive-fluid-css-type-scales/



## はじめに

Webデザインにおいて、文字サイズの一貫性とレスポンシブ性は重要な要素です。特に異なるデバイスやビューポートサイズに対して、スムーズに適応する文字サイズの実装は、多くの開発者が直面する課題となっています。

従来、このような適応型のタイポグラフィを実現するには、JavaScriptを使用したり、複雑なCSSの計算を行ったりする必要がありました。しかし、モダンなCSSの機能である`calc()`、`clamp()`、CSS変数を組み合わせることで、よりシンプルかつ効果的な解決策を実現できます。

## 前提知識

この記事を理解するために必要な知識：

- CSSの基本的な理解
- CSS変数（カスタムプロパティ）の基本
- レスポンシブデザインの概念
- モダンブラウザでのCSS機能サポート

## 実装の基本概念

### calc()関数の仕組み

`calc()`関数は数値計算を行うCSS関数ですが、いくつかの重要な制限があります：

1. 単位なしの数値とピクセル値の暗黙的な変換ができない
   - `calc(5px + 5)` は無効な式
   - `calc(5px + 5px)` のように単位をそろえる必要がある

2. ピクセル値同士の除算ができない
   - `calc(10px / 2px)` は無効
   - `calc(10px / 2)` のように除数は単位なしの数値にする

3. ピクセル値同士の乗算ができない
   - `calc(5px * 2px)` は無効
   - `calc(5px * 2)` のように片方は単位なしの数値にする

**これらの制限を回避するため、以下の巧みな戦略が紹介されています：**

```css
:root {
  /* 単位なしの数値として定義 */
  --min-size: 12;    /* 最小フォントサイズ */
  --max-size: 18;    /* 最大フォントサイズ */
  --container-min: 320;  /* 最小コンテナ幅 */
  --container-max: 2400; /* 最大コンテナ幅 */
}

.container-adaptive {
  /* サイズの計算 */
  --font-size: calc(
    /* 基本サイズ（単位なしの数値にピクセル単位を適用） */
    var(--min-size) * 1px +
    /* 最大サイズと最小サイズの差分 */
    (var(--max-size) - var(--min-size)) *
    /* コンテナサイズの現在位置を計算 */
    (100cqw - var(--container-min) * 1px) /
    /* コンテナサイズの範囲で正規化 */
    (var(--container-max) - var(--container-min))
  );
}
```

### clamp()関数による範囲の制御

`clamp()`関数は値を指定された範囲内に収める機能を提供します：

```css
.container-adaptive {
  /* clamp(最小値, 推奨値, 最大値) */
  font-size: clamp(
    var(--min-size) * 1px,    /* これより小さくならない */
    var(--font-size),         /* 可能ならこの値を使用 */
    var(--max-size) * 1px     /* これより大きくならない */
  );
}
```

この組み合わせにより、以下の利点が得られます：

1. 適切な範囲での制御
   - 最小値以下にならない
   - 最大値以上にならない
   - その間は計算された値を使用

2. スムーズな変化
   - ビューポートサイズの変化に応じて連続的に変化
   - 急激な変化を避けられる

## 実装パターン

### ビューポートベースの実装

ブラウザウィンドウ全体を基準にする場合：

```css
.viewport-adaptive {
  /* ビューポート幅に基づく計算 */
  --font-size: calc(
    var(--min-size) * 1px +
    (var(--max-size) - var(--min-size)) *
    /* 100vwはビューポート幅全体を表す */
    (100vw - var(--viewport-min) * 1px) /
    (var(--viewport-max) - var(--viewport-min))
  );
  font-size: clamp(
    var(--min-size) * 1px,
    var(--font-size),
    var(--max-size) * 1px
  );
}
```

### コンテナベースの実装

特定のコンテナ要素を基準にする場合：

```css
/* コンテナの定義 */
.container {
  /* コンテナクエリの有効化 */
  container-type: inline-size;
  container-name: main;
}

.container-adaptive {
  --font-size: calc(
    var(--min-size) * 1px +
    (var(--max-size) - var(--min-size)) *
    /* 100cqwは現在のコンテナ幅を表す */
    (100cqw - var(--container-min) * 1px) /
    (var(--container-max) - var(--container-min))
  );
  font-size: clamp(
    var(--min-size) * 1px,
    var(--font-size),
    var(--max-size) * 1px
  );
}

/* コンテナクエリの使用例 */
@container main (min-width: 400px) {
  .container-adaptive {
    /* コンテナが400px以上の時のスタイル */
  }
}
```

実装の際は、以下の点に考慮が必要かもしれません：
- デザインシステムの粒度
- コンポーネントの再利用性
- ブラウザサポートの要件（コンテナクエリは比較的新しい機能）

### スケーラブルなユーティリティクラスの作成

再利用可能なタイプスケールを構築するために、ユーティリティクラスを設定します：

```css
/* ベースとなる変数の定義 */
:root {
  --scale: 1.25;  /* スケール比率 */
  --base-size: 1rem;  /* 基準サイズ */

  /* ビューポート/コンテナの制約 */
  --min-viewport: 320;
  --max-viewport: 2400;
}

/* スケールレベルの定義 */
.text-md, .text-lg, .text-xl {
  container-type: inline-size;
}

/* 中サイズテキスト */
.text-md {
  --min-size: 14;
  --max-size: 16;
  font-size: clamp(
    var(--min-size) * 1px,
    calc(var(--min-size) * 1px + (var(--max-size) - var(--min-size)) *
    (100cqw - var(--min-viewport) * 1px) /
    (var(--max-viewport) - var(--min-viewport))),
    var(--max-size) * 1px
  );
}

/* 大サイズテキスト */
.text-lg {
  --min-size: 16;
  --max-size: 20;
  font-size: clamp(
    var(--min-size) * 1px,
    calc(var(--min-size) * 1px + (var(--max-size) - var(--min-size)) *
    (100cqw - var(--min-viewport) * 1px) /
    (var(--max-viewport) - var(--min-viewport))),
    var(--max-size) * 1px
  );
}

/* 特大サイズテキスト */
.text-xl {
  --min-size: 20;
  --max-size: 24;
  font-size: clamp(
    var(--min-size) * 1px,
    calc(var(--min-size) * 1px + (var(--max-size) - var(--min-size)) *
    (100cqw - var(--min-viewport) * 1px) /
    (var(--max-viewport) - var(--min-viewport))),
    var(--max-size) * 1px
  );
}
```

使用例：
```html
<div class="container">
  <h1 class="text-xl">大見出し</h1>
  <h2 class="text-lg">中見出し</h2>
  <p class="text-md">本文テキスト</p>
</div>
```

## 現在の制限事項

1. **ブラウザサポート**
   - コンテナクエリ（cqw）は比較的新しい機能
   - フォールバックの実装を検討する必要がある

2. **パフォーマンスへの影響**
   - 複雑な計算を含むため、大量の要素に適用する場合は注意が必要

注：参考記事ではcalc()関数でのrem単位の使用に制限があるとされていますが、現在の主要なモダンブラウザではこの制限は解除されており、rem単位を自由に使用できます。

## トラブルシューティング

デバグが大変だという感想を持ちました。

### ありそうな問題と解決方法

1. **計算結果が期待通りでない**
   - 単位の使用方法を確認（単位なしの数値とピクセル値の混在に注意）
   - clamp()の最小値と最大値が適切か確認

2. **スムーズな変化が見られない**
   - コンテナ/ビューポートの最小値と最大値の範囲が適切か確認
   - 中間値の計算が正しく行われているか確認

3. **古いブラウザでの対応**
```css
/* フォールバックの実装例 */
.adaptive-text {
  /* 基本サイズをフォールバックとして設定 */
  font-size: 16px;
  /* モダンブラウザ向けの実装 */
  font-size: clamp(var(--min-size) * 1px, var(--font-size), var(--max-size) * 1px);
}
```

## まとめ

フルードタイプスケールは、モダンなCSSの機能を活用することで、より柔軟でレスポンシブなタイポグラフィを実現します。本記事で紹介した実装方法には以下の利点があります：

- メンテナンス性の高いCSS変数の活用
- ブレイクポイントに依存しない連続的な変化
- コンポーザブルなユーティリティクラスによる柔軟な設定

これらの技術を適切に組み合わせることで、より洗練されたWebタイポグラフィを実現できます。

## 参考文献

- [Tobias Ahlin - Responsive fluid CSS type scales](https://tobiasahlin.com/blog/responsive-fluid-css-type-scales/)
- [Bendable - フルードタイプスケールライブラリ](https://github.com/tobiasahlin/bendable)
- [MDN Web Docs - calc()](https://developer.mozilla.org/ja/docs/Web/CSS/calc)
- [MDN Web Docs - clamp()](https://developer.mozilla.org/ja/docs/Web/CSS/clamp)
- [MDN Web Docs - CSS カスタムプロパティ（変数）の使用](https://developer.mozilla.org/ja/docs/Web/CSS/Using_CSS_custom_properties)
- [MDN Web Docs - CSS コンテナクエリ](https://developer.mozilla.org/ja/docs/Web/CSS/CSS_Container_Queries)
- [W3C - CSS Values and Units Module Level 4](https://www.w3.org/TR/css-values-4/#calc-type-checking)