🧭 はじめに
要約
本記事は、CSSのclamp()を活用した流体レスポンシブデザインにおいて、Web制作者が直面する単位変換の課題(px/pt混在・デザインツール間の基準差異・計算精度)を、Sassの関数設計によって解決する方法を提案します。
単なる変換ツールに留まらず、remove-unit()による単位の安全な抽象化、$default-dpiによる柔軟なpt変換、そしてr-clamp()/r-clamp-pt()による統一的な流体設計を通じて、デザインツールとWeb実装のギャップを埋める「小さな設計思想」について解説します。
これにより、コーディングの堅牢性・保守性・再利用性を高め、より効率的で高品質なフロントエンド開発を実現します。
背景
レスポンシブ対応のデザインを実装していると、
「フォントサイズや余白をビューポートに応じて自然に変化させたい」
という場面はよくあります。
CSSのclamp()を使えば簡単に実現できますが、
実際の案件で使うときは「単位の違い」「計算精度」「デザインツールの基準(px / pt)」など、
一見些細ですが、無視できない課題に直面します。
そして、CanvaやIllustratorなど、px基準ではないデザインデータ(pt単位など)をもとにコーディングすることは、実務においてよくあります。
特にフリーランスや受託案件では、SPデザインが存在しないケースも少なくありません。
その際、PCデザインのpt単位から、SPのpx値を手動で計算し、さらに可変させるためのclamp()式を組むのは、非常に手間とミスの温床となりがちです。
この記事では、そういった課題を整理しながら
Sass関数を“設計する”アプローチで関数群を構築していきます。
単なるpx→rem変換にとどまらず、pt単位や可変レイアウト(r-clamp)までを一貫して扱える仕組みになっています。
目次
はじめに
目的と概要
実際の動作サンプル
実装のポイント
コード全体
補足:calc()ではなくmath.div()を使う理由
全体のまとめ
今後の展開
おわりに
🎯 目的と概要
この記事で実装する関数は次のような用途に対応します。
| 関数名 | 機能 | 想定用途 |
|---|---|---|
remove-unit() |
単位を安全に除去 | 内部処理用 |
px-to-rem() |
px → rem変換 | フォントサイズなど |
px-to-vw-sp() / px-to-vw-pc() |
px → vw変換 | SP / PC用デザイン |
r-clamp() |
px単位の流体設計 | 通常のレスポンシブ対応 |
pt-to-*() 系 |
pt(ポイント)基準で変換 | Canvaや印刷デザインとの併用 |
🧪 実際の動作サンプル
注意事項
- 使用例に記載されているコードは、後述するコード全体をSCSSに読み込んでいないと動きません。
-
Sassのバージョンによっては、
px-to-vw-pc()およびpt-to-vw-pc()関数がコンパイルエラーとなる場合があります。
これは、min()/max()内で px と vw など異なる単位を混在させているためです。
その場合は、min()/max()の代わりにclamp()式を使うか、Sassを最新バージョン(Dart Sass 1.60以降)に更新してください。
使用例の環境
- 最小画面幅:375
- 最大画面幅:1440
- 基準フォントサイズ:16
- dpi:96
基本的な使用例
html
<div class="demo">
<h1 class="demo__title">Fluid Title</h1>
<p class="demo__caption">Resizes smoothly with viewport width.</p>
</div>
scss
.demo__title {
// px値で指定する場合
font-size: r-clamp(20, 40);
}
.demo__caption {
// pt値で指定する場合
// dpiの設定は$default-dpiで行う
// 印刷用のptなら$default-dpiを300に変更する
font-size: r-clamp-pt(10, 18);
}
出力css
.demo__title {
font-size: clamp(1.25rem, 0.8098591549rem + 1.8779342723vw, 2.5rem);
}
.demo__caption {
font-size: clamp(0.8333333333rem, 0.5985915493rem + 1.0015649452vw, 1.5rem);
}
ブラウザの幅を変えると、タイトルとキャプションが滑らかに変化します。
応用例:Canvaデザイン(pt単位)をr-clampに対応させる
Canvaのデザインはpt単位なので、r-clampに直接pt→px変換を組み合わせると便利に使えます。
html
<div class="demo">
<h1 class="demo__title">Fluid Title</h1>
</div>
scss
// PCデザイン:確定値(Canvaの24ptをpx換算)
// SPデザイン:仮値(16px)
.demo__title {
font-size: r-clamp(16px, pt-to-px(24pt));
}
出力css
.demo__title {
font-size: clamp(1rem, 0.6478873239rem + 1.5023474178vw, 2rem);
}
✅ PCかSP、片方のデザインしかないときでも、可変挙動を実際のブラウザ上で確認可能です。
レビューや仮モック作成にとても役立ちます。
💡 実装のポイント
本章では、今回のSass関数群を設計する上での思想と、
実装上のポイントを整理します。
1. 数値の安全な抽象化 — remove-unit()
Sassで単位付きの値を扱うとき、math.div()などの演算で
「単位エラー」が発生することがあります。
remove-unit()関数では、Sass公式のmath.is-unitless()を使って
単位の有無を安全に判定し、必要に応じて除去します。
これにより、px・pt・remが混在しても内部計算を一貫して数値で扱えるようになります。
✅ メリット
"単位変換関数同士を安全に組み合わせられる"
"意図しない単位付与によるエラーを防止"
2. 流体設計の一貫化 — r-clamp() / r-clamp-pt()
r-clamp()は、デザインの最小・最大サイズを指定し、
ビューポート幅に応じて滑らかに変化させるためのSass関数です。
Sass内でmath.div()を使用し、スケールとオフセットを数値演算して
最終的にclamp()式を生成します。
これにより、中間値を文字列ではなく数値として安全に計算できます。
さらにr-clamp-pt()を追加することで、
Canva・Illustratorなどpt基準のデザインにも対応可能にしています。
| 関数 | 基準単位 | 用途 |
|---|---|---|
r-clamp() |
px | 通常のWebデザイン |
r-clamp-pt() |
pt | ptベースのデザイン |
💡 $default-dpiを切り替えることで、Web/印刷デザイン両対応が可能です。
3. デザイン基準の差を吸収 — pt-to-*() 系列
デザインツールによって「1pt = 何pxか」が異なるため、
単純な換算ではスケールがずれる場合があります。
この課題に対し、$default-dpiをグローバル変数として定義し、
プロジェクトごとに基準を変更できるようにしています。
| 環境 | 想定dpi | 主な用途 |
|---|---|---|
| Canva | 96 | Webデザイン |
| Figma / Illustrator | 72 | 画面デザイン |
| 印刷用 | 300 | 高解像度出力(DTP) |
✅ メリット
"デザイン元の単位をそのまま活かして計算可能"
"72 / 96 / 300dpi などを切り替えるだけで一括調整できる"
4. math.div() × calc() の役割分離
以前のSassでは、calc()文字列を使って式を組む方法が一般的でした。
しかし、calc()は文字列処理のため、途中の数値演算は行えません。
本記事では、Sass内部ではmath.div()で数値演算を行い、
CSS出力時のみcalc()を使用する方針を採用しています。
| 処理フェーズ | 使用関数 | 役割 |
|---|---|---|
| Sass内部演算 | math.div() |
単位安全・高精度な計算 |
| CSS出力 | calc() |
ブラウザによる動的演算 |
🎯 結果として、開発時の計算精度と出力時の柔軟性を両立しています。
5. 設定の一元化 — グローバル変数
すべての関数で共通して使用する値(ブレークポイント・DPI・基準フォントサイズなど)は
グローバル変数としてまとめています。
$default-min-bp: 375;
$default-max-bp: 1440;
$root-font-size: 16;
$default-dpi: 96;
これにより、1か所を変更するだけで全関数の計算結果を更新でき、
プロジェクトごとの調整が容易になります。
🧩 まとめ:小さな設計思想としてのSass関数群
この関数群の目的は単なる単位変換ではなく、
異なるデザイン基準を安全に統合する“小さなフレームワーク”を作ることです。
単位の抽象化 → 流体設計の一貫化 → DPI対応 → 数値演算の安全化
という流れを通じて、
Web制作における設計精度と再利用性を高めるSass設計を目指しています。
💡 コード全体
コード全体を見る
@use "sass:math";
// =========================
// グローバル変数定義→ここを調整すれば、複数の関数の値も一括で変更できます。
// =========================
$default-min-bp: 375;
$default-max-bp: 1440;
$root-font-size: 16;
/// 📘 pt-to-px変換の基準となるDPI値(Canva:96, 印刷:300, Figma:72など、実データに合わせて調整)
$default-dpi: 96;
// =========================
// 汎用ユーティリティ
// =========================
/// 📘 単位を安全に除去して、純粋な数値を返す関数。
@function remove-unit($value) {
// math.is-unitless() を使用することで、単位の有無をより堅牢にチェックします。
@if not math.is-unitless($value) {
@return math.div($value, $value * 0 + 1);
}
@return $value;
}
// =========================
// ピクセル変換関連
// =========================
/// 📘 pxをremに変換する関数
@function px-to-rem($px, $baseFontSize: $root-font-size){
// 入力された数値に単位(px)が付いていた場合でも、remove-unit関数で安全に処理する
$px: remove-unit($px);
$baseFontSize: remove-unit($baseFontSize);
$rem: math.div($px, $baseFontSize);
@return $rem * 1rem;
}
/// 📘 pxをvwに変換します。
// 内部利用を想定しているため、関数名の先頭に _ を付けています。
@function _px-to-vw($px, $viewport) {
@return math.div(remove-unit($px), remove-unit($viewport)) * 100vw;
}
/// 📘 SP用のvwサイズを返却
@function px-to-vw-sp($px, $minViewport: $default-min-bp) {
@return _px-to-vw($px, $minViewport);
}
/// 📘 pc用のvwサイズを返却
@function px-to-vw-pc($px, $maxViewport: $default-max-bp) {
// 入力された数値に単位(px)が付いていた場合でも、remove-unit関数で安全に処理する
$px: remove-unit($px);
@if $px < 0 {
@return max($px * 1px, _px-to-vw($px, $maxViewport));
} @else {
@return min($px * 1px, _px-to-vw($px, $maxViewport));
}
}
// =========================
// r-clamp(流体タイポグラフィ)
// =========================
/// 📘 サイズが画面大きさに応じて変化するCSSのclamp()関数を生成します。
@function r-clamp($min, $max, $minViewport: $default-min-bp, $maxViewport: $default-max-bp, $baseFontSize: $root-font-size) {
// 入力された数値に単位(px)が付いていた場合でも、remove-unit関数で安全に処理する
$min: remove-unit($min);
$max: remove-unit($max);
$minViewport: remove-unit($minViewport);
$maxViewport: remove-unit($maxViewport);
$baseFontSize: remove-unit($baseFontSize);
$vwScale: math.div(($max - $min), ($maxViewport - $minViewport)); // vw単位でのスケールを計算
$baseOffset: $min - $minViewport * $vwScale; // 基準となる最小値からのオフセットを計算
$minRem: math.div($min, $baseFontSize); // 最小値をremに変換
$maxRem: math.div($max, $baseFontSize); // 最大値をremに変換
$baseOffsetRem: math.div($baseOffset, $baseFontSize); // オフセットをremに変換
$vwScaleRem: $vwScale * 100; // vwスケールを調整
@return clamp(#{$minRem}rem, calc(#{$baseOffsetRem}rem + #{$vwScaleRem}vw), #{$maxRem}rem);
}
// =========================
// ポイント変換関連
// =========================
/// 📘 可変DPI対応
@function pt-to-px-rate($dpi: $default-dpi) {
$dpi: remove-unit($dpi);
@return math.div($dpi, 72); // 1pt = dpi/72 px
}
/// 📘 ポイント(pt)を単位なしのピクセル(px)値に変換します。
@function pt-to-px-value($pt){
// 入力された数値に単位(pt)が付いていた場合でも、remove-unit関数で安全に処理する
$pt: remove-unit($pt);
@return $pt * pt-to-px-rate();
}
/// 📘 ポイント(pt)をpx単位の値に変換します。
@function pt-to-px($pt){
@return pt-to-px-value($pt) * 1px;
}
/// 📘 ポイント(pt)をrem単位の値に変換します。
@function pt-to-rem($pt, $baseFontSize: $root-font-size){
$px: pt-to-px-value($pt);
// 入力された数値に単位(px)が付いていた場合でも、remove-unit関数で安全に処理する
$baseFontSize: remove-unit($baseFontSize);
$rem: math.div($px, $baseFontSize);
@return $rem * 1rem;
}
/// 📘 ポイント(pt)をspサイズ用のvw単位の値に変換します。
@function pt-to-vw-sp($pt, $minViewport: $default-min-bp) {
// pt値をpx値に変換する
$px: pt-to-px-value($pt);
@return _px-to-vw($px, $minViewport);
}
/// 📘 ポイント(pt)をpcサイズ用のvw単位の値に変換します。
@function pt-to-vw-pc($pt, $maxViewport: $default-max-bp) {
// pt値をpx値に変換する
$px: pt-to-px-value($pt);
@if $px < 0 {
@return max($px * 1px, _px-to-vw($px, $maxViewport));
} @else {
@return min($px * 1px, _px-to-vw($px, $maxViewport));
}
}
// =========================
// r-clamp-pt(流体タイポグラフィ)
// =========================
/// 📘 ptに対応したサイズが画面の大きさに応じて変化するCSSのclamp()関数を生成します。
@function r-clamp-pt($minPt, $maxPt, $minViewport: $default-min-bp, $maxViewport: $default-max-bp, $baseFontSize: $root-font-size) {
//ポイント値をピクセル値に変換する
$min: pt-to-px-value($minPt);
$max: pt-to-px-value($maxPt);
// 入力された数値に単位(px)が付いていた場合でも、remove-unit関数で安全に処理する
$minViewport: remove-unit($minViewport);
$maxViewport: remove-unit($maxViewport);
$baseFontSize: remove-unit($baseFontSize);
$vwScale: math.div(($max - $min), ($maxViewport - $minViewport)); // vw単位でのスケールを計算
$baseOffset: $min - $minViewport * $vwScale; // 基準となる最小値からのオフセットを計算
$minRem: math.div($min, $baseFontSize); // 最小値をremに変換
$maxRem: math.div($max, $baseFontSize); // 最大値をremに変換
$baseOffsetRem: math.div($baseOffset, $baseFontSize); // オフセットをremに変換
$vwScaleRem: $vwScale * 100; // vwスケールを調整
@return clamp(#{$minRem}rem, calc(#{$baseOffsetRem}rem + #{$vwScaleRem}vw), #{$maxRem}rem);
}
🧩 補足:calc()ではなくmath.div()を使う理由
以前は以下のように calc() 文字列で数式を組んでいました。
$vwScale: "calc((#{$max} - #{$min}) / (#{$maxViewport} - #{$minViewport}))";
変更したのには大きく2つの理由があります。
1つ目は、Sass公式ドキュメントで、math.div() の利用が推奨されているためです。
公式がmath.div() の利用を推奨する理由は下記の通りです。
| 理由 | 内容 |
|---|---|
| ✅ 型安全 | Sass上で数値として扱えるため単位ミスを防げる |
| ✅ 最適化 | コンパイル時に値を確定できる |
| ✅ 仕様準拠 |
/ 演算子が非推奨になったため代替が必要 |
計算式をmath.div()に変更した結果として、r-clamp()を他の関数からも安全に呼び出せるようになりました。
これにより、コード全体の再利用性と堅牢性が大幅に向上しました。
2つ目は、Sass関数内の計算では、calc() は単なる文字列として扱われるため、
演算途中での正確な値計算や変数再利用ができないためです。
繰り返しになりますが、 math.div() は、Sassの内部演算で確実に数値として扱えるため、
「clamp式を生成するための中間値(vwScaleやbaseOffsetRemなど)」を安全に算出できます。
そのため、下記の住み分けが最も安定します。
-
math.div():数値処理 → 精密なSassロジック -
calc():最終出力でのみ使用 → CSS演算子として機能
以上の理由からcalc()ではなくmath.div()を使うことにしました。
💬 全体のまとめ
今回のSass関数群は、単なる“単位変換ツール”ではなく、
デザインツールとブラウザのギャップを安全に吸収する小さな設計基盤です。
特に:
-
remove-unit()による 安全な数値抽象化 -
r-clamp()/r-clamp-pt()による 流体設計の統一 -
$default-dpiによる Web/印刷両対応の拡張性
これらの工夫によって、「見た目の再現」から「設計の再現」へという発想転換が可能になります。
Sassによるコーディングを“プログラム設計”として捉える足がかりです。
🧠 今後の展開
次回は以下のテーマでの発展も検討中です。
-
emや%単位対応の拡張版 -
map構文を使ったレスポンシブ定義の一元化
🚀 おわりに
この関数群は、「実務上のデザイン支給の不均一さ」をきっかけに生まれました。
SPやPCどちらか一方のデザインしか存在しない場合でも、
仮値を用いた検証や柔軟なレスポンシブ調整が可能です。
単なる変換ツールではなく、
「デザインから実装へ橋渡しする小さなフレームワーク」として活用できるはずです。
📘 この記事が役立ったら、いいね・ストックで応援してもらえると嬉しいです!
質問や改善提案はぜひコメント欄でどうぞ。