1. ホーム
  2. Sass

【Sass】sass:math の使い方|/ の割り算を math.div に置き換える方法と math.round・percentage

Share

Sass で $width / 3 のように割り算を書いてコンパイルすると、DEPRECATION WARNING [slash-div] という警告が出ることがあります。これは Dart Sass 1.33.0 から、/ による割り算が非推奨になったためです。この記事では、なぜ非推奨になったのかという背景から、代わりに使う math.div() の書き方、math.round() や math.percentage() など sass:math モジュールのよく使う関数、px を rem に変換する関数の実例、古いコードを sass-migrator でまとめて書き換える手順までを、コンパイル後の CSS と並べて解説します。記法は SCSS、コンパイラは Dart Sass を前提にしています。

slash-div の警告はなぜ出るのか

まずは警告が出る状況を再現してみます。960px の幅を3等分するつもりで、変数どうしを / で割っています。

style.scss
$width: 960px;
$cols: 3;

.column {
  width: $width / $cols;
}

これをコンパイルすると、CSS は width: 320px; と期待どおりに出力されますが、ターミナルには次の警告が表示されます。

ターミナル
DEPRECATION WARNING [slash-div]: Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($width, $cols) or calc($width / $cols)

More info and automated migrator: https://sass-lang.com/d/slash-div

  ╷
5 │   width: $width / $cols;
  │          ^^^^^^^^^^^^^^
  ╵
    style.scss 5:10  root stylesheet

メッセージの意味は「calc() の外で / を割り算に使うのは非推奨で、Dart Sass 2.0.0 で削除される」です。今はまだ計算してくれますが、将来のバージョンでは割り算として扱われなくなります。

CSS では / が「区切り記号」として使われている

非推奨になった理由は、CSS 自体が / を区切り記号として使っているからです。font: 16px/1.5 sans-serif; の / はフォントサイズと行の高さを区切る記号ですし、grid-row: 1 / 3; や aspect-ratio: 16 / 9;、rgb(0 0 0 / 50%) の透明度の指定でも / は割り算ではありません。CSS の仕様が新しくなるたびに、こうした使われ方は増えています。

Sass はこれまで、/ を「割り算」と「区切り記号」のどちらとして扱うかを、前後に変数があるか、カッコで囲まれているか、といった細かいルールで判断していました。実際に数値だけを並べた場合と、カッコで囲んだ場合を比べると、結果が変わります。

style.scss
.box {
  margin: 16px / 2;   // 数値だけ → 区切り記号として出力される
  padding: (16px / 2); // カッコで囲む → 割り算になる(警告あり)
}
style.css
.box {
  margin: 16px/2;
  padding: 8px;
}

見た目はほとんど同じなのに、片方は計算され、片方は文字のまま出力されます。この分かりにくさを解消するため、Sass は「/ は区切り記号」「割り算は専用の関数 math.div() で書く」とはっきり分けることにしました。これが slash-div の非推奨化です。

math.div() で割り算を書く

math.div() は sass:math という組み込みモジュールに入っている関数です。使う前にファイルの先頭で @use 'sass:math'; と読み込み、math.div(割られる数, 割る数) の形で呼び出します。

style.scss
@use 'sass:math';

$width: 960px;
$cols: 3;

.column {
  width: math.div($width, $cols);       // px ÷ 数値 → px が残る
}

.ratio {
  line-height: math.div(24px, 16px);    // px ÷ px → 単位が消える
}

.half {
  width: math.div(100%, 3);
}
style.css
.column {
  width: 320px;
}

.ratio {
  line-height: 1.5;
}

.half {
  width: 33.3333333333%;
}

単位の扱いは算数の割り算と同じです。960px ÷ 3 なら px が残り、24px ÷ 16px なら px どうしが打ち消し合って単位のない 1.5 になります。この「単位が消える」性質は、あとで紹介する px→rem 変換で活躍します。

なお、警告文の Recommendation にあるとおり、calc($width / $cols) と書く方法もあります。calc() の中の / は割り算として扱われ、Sass がコンパイル時に計算できる値ならそのまま数値になります(calc(960px / 3) は 320px と出力されます)。calc((100% - 48px) / 3) のように単位が混ざってコンパイル時に計算できない場合は、calc() のまま CSS に出力され、ブラウザーが計算します。

sass:math のよく使う関数

sass:math には割り算以外にも、数値を扱う関数がそろっています。コーディングでよく使うものを表にまとめました。

関数・変数働き例と結果
math.div($a, $b)割り算math.div(960px, 3) → 320px
math.round($n)四捨五入して整数にするmath.round(12.5px) → 13px
math.ceil($n)切り上げmath.ceil(12.1px) → 13px
math.floor($n)切り捨てmath.floor(12.9px) → 12px
math.percentage($n)単位のない数値を % に変換するmath.percentage(0.25) → 25%
math.max($n...)いちばん大きい値を返すmath.max(10px, 24px, 16px) → 24px
math.min($n...)いちばん小さい値を返すmath.min(10px, 24px, 16px) → 10px
math.clamp($min, $n, $max)$n を $min〜$max の範囲に収めるmath.clamp(320px, 480px, 400px) → 400px
math.abs($n)絶対値math.abs(-8px) → 8px
math.pow($base, $exp)累乗math.pow(2, 3) → 8
math.sqrt($n)平方根math.sqrt(16) → 4
math.$pi円周率3.1415926536
math.$e自然対数の底2.7182818285

いずれもコンパイル時に計算される関数なので、出力される CSS には計算結果の数値だけが残ります。ここからは、特に出番の多い関数を実際のコードで見ていきます。

math.round・ceil・floor で端数を処理する

割り算をすると、割り切れない値は小数点以下10桁まで出力されます。px の値ならピクセル単位に揃えたほうが扱いやすいので、math.round()(四捨五入)、math.ceil()(切り上げ)、math.floor()(切り捨て)で整数にします。単位は保たれたまま丸められます。

style.scss
@use 'sass:math';

$container: 1000px;

.thumb {
  width: math.div($container, 3);                  // 丸めない
  height: math.floor(math.div($container, 3));     // 切り捨て
  min-height: math.ceil(math.div($container, 3));  // 切り上げ
}

.text {
  // 小数第2位までに丸める(100 倍して丸め、100 で割り戻す)
  line-height: math.div(math.round(math.div(28, 18) * 100), 100);
}
style.css
.thumb {
  width: 333.3333333333px;
  height: 333px;
  min-height: 334px;
}

.text {
  line-height: 1.56;
}

math.round() には桁数を指定する引数がありません。小数第2位まで残したいときは、.text の例のように 100 倍してから丸め、math.div() で 100 に割り戻します。

math.percentage で比率を % にする

math.percentage() は、0.25 のような単位のない数値を 100 倍して % を付けた値を返します。math.div() と組み合わせると「全体の何分のいくつか」をそのまま幅に変換できるので、カラムの幅を作るときに便利です。

style.scss
@use 'sass:math';

$grid-columns: 4;

@for $i from 1 through $grid-columns {
  .col-#{$i} {
    width: math.percentage(math.div($i, $grid-columns));
  }
}
style.css
.col-1 {
  width: 25%;
}

.col-2 {
  width: 50%;
}

.col-3 {
  width: 75%;
}

.col-4 {
  width: 100%;
}

引数に単位の付いた数値(50px など)を渡すとエラーになります。math.percentage() に渡すのは、math.div() で単位を打ち消したあとの比率だけ、と覚えておきましょう。

math.max・math.min・math.clamp で値を選ぶ

math.max() と math.min() は、渡した値の中からいちばん大きい値、いちばん小さい値を返します。math.clamp($min, $number, $max) は、$number が範囲を超えていれば上限か下限に収めた値を返します。引数の並びは CSS の clamp() と同じです。

style.scss
@use 'sass:math';

$header-height: 64px;
$sticky-offset: 80px;

.anchor {
  // 2つの変数のうち大きいほうを採用する
  scroll-margin-top: math.max($header-height, $sticky-offset);
}

.icon {
  // 計算結果が 32px〜48px の範囲から出ないようにする
  width: math.clamp(32px, $header-height * 0.9, 48px);
}
style.css
.anchor {
  scroll-margin-top: 80px;
}

.icon {
  width: 48px;
}

$header-height * 0.9 は 57.6px で、上限の 48px を超えているため 48px に収まっています。これらは CSS の max() / min() / clamp() と名前が似ていますが、ブラウザーではなくコンパイル時に値を1つに決める点が違います。この違いは、後半のつまずきポイントでもう一度取り上げます。

math.$pi で円周の長さを求める

sass:math には関数だけでなく、math.$pi(円周率)や math.$e といった定数も用意されています。たとえば SVG の円で進捗リングを作るとき、線の長さ(stroke-dasharray)には円周の長さ 2πr が必要です。これを Sass に計算させることができます。

style.scss
@use 'sass:math';

$radius: 40px;
$circumference: 2 * math.$pi * $radius; // 円周の長さ

.progress-ring__circle {
  stroke-dasharray: $circumference;
  stroke-dashoffset: $circumference * 0.25; // 25% ぶん線を隠す
}
style.css
.progress-ring__circle {
  stroke-dasharray: 251.3274122872px;
  stroke-dashoffset: 62.8318530718px;
}

円周率を 3.14 と手で書くよりも正確で、半径を変えたときも変数を1か所直すだけで済みます。

実例|px を rem に変換する関数を作る

math.div() がいちばん活躍するのが、デザインカンプの px 指定を rem に変換する関数です。ルートの文字サイズ(ブラウザーの初期値は 16px)で割って単位を消し、そこに 1rem を掛けて rem の値にします。関数そのものの書き方は @function の記事で詳しく扱っているので、ここでは計算の中身に注目してください。

style.scss
@use 'sass:math';

$root-font-size: 16px;

// px を rem に変換する
@function rem($px) {
  @return math.div($px, $root-font-size) * 1rem;
}

.title {
  font-size: rem(28px);
  margin-bottom: rem(20px);
  letter-spacing: rem(1.2px);
}

.lead {
  font-size: rem(15px);
}
style.css
.title {
  font-size: 1.75rem;
  margin-bottom: 1.25rem;
  letter-spacing: 0.075rem;
}

.lead {
  font-size: 0.9375rem;
}

math.div(28px, 16px) は px どうしの割り算なので 1.75 という単位のない数値になり、* 1rem で 1.75rem になります。古い記事では同じ関数が $px / $root-font-size * 1rem と書かれていることが多く、そのまま使うと slash-div の警告が出ます。見つけたら math.div() の形に書き換えておきましょう。

sass-migrator division で既存コードをまとめて書き換える

過去に書いた SCSS に / の割り算がたくさん残っている場合、1か所ずつ直すのは大変です。Sass 公式の移行ツール sass-migrator には、/ の割り算を自動で書き換える division というコマンドがあります。npm でインストールして使います。

ターミナル
# インストール(グローバルに入れる場合)
npm install -g sass-migrator

# 書き換え結果を確認するだけ(ファイルは変更しない)
sass-migrator division --dry-run --verbose style.scss

# 実際に書き換える
sass-migrator division style.scss

いきなり書き換えるのが不安なときは、--dry-run を付けて結果を確認してから実行すると安心です。--verbose を一緒に付けると、書き換え後の内容が表示されます。たとえば次のようなファイルを用意して実行してみます。

style.scss(書き換え前)
$base: 16px;
$gutter: 24px;

.card {
  padding: $gutter / 2;
  font-size: 14px / $base * 1rem;
  font: 16px/1.5 sans-serif;
}
style.scss(書き換え後)
@use "sass:math";

$base: 16px;
$gutter: 24px;

.card {
  padding: $gutter * 0.5;
  font-size: math.div(14px, $base) * 1rem;
  font: 16px/1.5 sans-serif;
}

先頭に @use "sass:math"; が自動で追加され、割り算は math.div() に置き換わっています。$gutter / 2 のように定数で割っている箇所は、* 0.5 という掛け算に変換されることもあります。一方、font プロパティの 16px/1.5 は区切り記号なので、そのまま残っています。

@use や @import で読み込んでいるパーシャルもまとめて書き換えたいときは、--migrate-deps(短縮形は -d)を付けて、エントリーポイントとなるファイルを指定します。書き換えたあとは差分を確認し、コンパイル結果が変わっていないかを確かめておきましょう。

math.div に置き換えるときにつまずくところ

There is no module with namespace “math”. と出る

math.div() を書いたファイルに @use 'sass:math'; がないと、このエラーになります。@use は書いたファイルの中でしか効かないので、パーシャルに分けている場合は、math.div() を使っているパーシャルそれぞれの先頭に書く必要があります。メインのファイルで1回読み込めば全体で使える、というわけではない点に注意してください。

math.max(10px, 2vw) が incompatible units になる

math.max() や math.min()、math.clamp() はコンパイル時に値を比べるため、px と vw のように換算できない単位どうしを渡すと Error: 10px and 2vw have incompatible units. になります。vw は画面幅が決まらないと px に直せないので、Sass には比べようがないのです。

画面幅に応じて変わる値を選びたいなら、それはブラウザーに任せる仕事です。名前空間を付けずに max(10px, 2vw) と書けば、Sass は CSS の max() としてそのまま出力します。逆に max(10px, 24px) のように単位がそろっていて計算できる場合は、Sass が先に計算して 24px だけを出力します。

style.scss
.heading {
  font-size: max(10px, 2vw);   // CSS の max() として残る
  margin-top: max(10px, 24px); // 計算できるので 24px になる
}
style.css
.heading {
  font-size: max(10px, 2vw);
  margin-top: 24px;
}

node_modules のライブラリから警告が大量に出る

自分のコードは書き換えたのに、読み込んでいる古い CSS フレームワークの SCSS から slash-div の警告が出続けることがあります。ライブラリ側のコードは自分では直せないので、まずはライブラリを新しいバージョンに更新できないか確認します。すぐに更新できない場合は、Dart Sass のコマンドラインで --quiet-deps オプションを付けると、ロードパス(--load-path で指定した node_modules など)経由で読み込んだ依存ファイルからの警告を表示しないようにできます。自分のファイルの警告はこれまでどおり表示されるので、直すべき箇所を見落とすことはありません。

node-sass(LibSass)では math.div が使えない

sass:math などの組み込みモジュールと @use は Dart Sass の機能で、開発が終了した LibSass(npm の node-sass パッケージ)では使えません。プロジェクトが node-sass でコンパイルしている場合、math.div() に書き換えるとかえって動かなくなります。package.json の依存関係を確認し、node-sass が入っていたら、先に npm の sass パッケージ(Dart Sass)へ切り替えてから書き換えを行ってください。

まとめ

Dart Sass 1.33.0 以降、/ による割り算は非推奨になり、使うと DEPRECATION WARNING [slash-div] が出ます。CSS では font や grid-row、aspect-ratio などで / が区切り記号として使われているため、割り算は sass:math モジュールの math.div() で書くように分けられました。@use 'sass:math'; で読み込めば、math.round()・math.ceil()・math.floor() での端数処理、math.percentage() での比率の変換、math.max()・math.min()・math.clamp() での値の選択、math.$pi を使った計算もできます。px を rem に変換する関数は math.div($px, 16px) * 1rem の形で書き、既存のコードは sass-migrator division でまとめて移行できます。

参考ページ