0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Sassでデザイン単位を統一する:r-clampとpt変換関数でCanva・IllustratorのDPI差を吸収する

Posted at

🧭 はじめに

要約

本記事は、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や印刷デザインとの併用

🧪 実際の動作サンプル

注意事項

  1. 使用例に記載されているコードは、後述するコード全体をSCSSに読み込んでいないと動きません。
  2. 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上で数値として扱えるため単位ミスを防げる
✅ 最適化 コンパイル時に値を確定できる
✅ 仕様準拠 / 演算子が非推奨になったため代替が必要

参考:Sass公式 - math.div()

計算式を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どちらか一方のデザインしか存在しない場合でも、
仮値を用いた検証や柔軟なレスポンシブ調整が可能です。

単なる変換ツールではなく、
デザインから実装へ橋渡しする小さなフレームワーク」として活用できるはずです。


📘 この記事が役立ったら、いいね・ストックで応援してもらえると嬉しいです!
質問や改善提案はぜひコメント欄でどうぞ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?