レスポンシブな空白のつくり方 第1回 clamp()とvwを利用した伸び縮みする空白

レスポンシブなウェブサイトを作るときに、セクションやブロックの空白を調整するというのがよくあります。今回はメディアクエリーを使わずに、clamp()とvwを使った調整方法を紹介します。

発行

著者 矢倉 眞隆 フロントエンド・エンジニア
レスポンシブな空白のつくり方 シリーズの記事一覧

はじめに

レスポンシブなウェブサイトを作るときに、考えないといけないことはいろいろあります。ブレークポイントをどうするか。サイドバーやカラムレイアウトなど、PCでは横並びのレイアウトをスマートフォンの画面でどうするか。アイコンやイラストの解像度はどうするか。写真などもアスペクト比の違うものをどう出し分けるか。これらはデザイン段階でも、コードを書く段階でも考えないといけません。

中でも筆者がコードを書くときに気にすることに、ブロックの間の空白があります。この記事では、レスポンシブなウェブサイトを作るときの、このブロック間の空間について、考えたいと思います。

スマホで空きすぎる空白

今回紹介する例は、ピクセルグリッドのウェブサイトで使っているものです。ピクセルグリッドのウェブサイトは、2018年の末にリニューアルしました。見た目こそ大きく変えないものの、開発環境を見直し、フォントの調整やコード量の削減を行っています。

以降も開発環境の再度の見直しやCSSの調整、コンテンツの追加などを継続的に行っています。

なかでも大きな変更として、デザインツールがAdobe IllustratorからFigmaになったことが挙げられます。Figmaへの移行により、コンポーネント化、共通化をそれまでよりも強く意識した作りになりました。加えて、デザインシステムという考え方も意識し、空白の大きさもデザイントークンとして定義されました。

Figmaで定義した空白のデザイントークン。4px、8px、12px、20px、32px、52px、84px、136pxの8つがある。

デザイントークンの数値

ここで定義している空白のデザイントークンの数値は、鈴木丈さんが述べていた「8pxに対してフィボナッチ数列をかけていく」という方法を参考にしています。詳しくは次のサイトを参照してください。また、CodeGridの座談会記事でも、話題になっています。

業務内容を紹介するページでは、セクションの間の空白に136pxを使っています。

ゆったりした感じを出せているかと思います。では、スマートフォンではどうでしょう。

すこし隙間が空きすぎているように感じます。このため、幅が細い場合には52pxを使うことにしました。ではそれをコードで表しましょう。

メディアクエリーを多用したくない理由

ウインドウの幅に応じてCSSを出し分けたいときに、まず思いつくのがメディアクエリーです。ブレークポイントを決め、それ以上(以下)の場合に、宣言を上書きするようなCSSを書いて出し分けします。

たとえば、通常は52px、幅1024px以上の場合は136pxの空白にするには、次のようなコードになります。

メディアクエリーで空白を出し分ける

section + section {
  margin-top: 52px;
}
@media screen and (min-width: 1024px) {
  section + section {
    margin-top: 136px;
  }
}

メディアクエリについて

メディアクエリの基本的な使い方については、次の記事に詳細があります。

レスポンシブなサイトを作った方であれば、何度も書いてきたコードではないでしょうか。しかし、筆者はメディアクエリーを使った調整があまり好きではありません。

理由の1つは、ブレークポイント前後で空白が大きく変わってしまうことです。今回が極端な例で、768pxを境に、52px―136pxと、80px以上も値が変わってしまいます。ウインドウの大きさによって大きな変化が起こってしまうのは、あまりうれしくありません。

もう1つの理由は、管理性によるものです。メディアクエリーは特定条件下でコードの「上書き」をすることに使われることが多いです。「ちょっとこのコードだとスマホじゃ都合が悪いから、上書きしちゃおう」と、コードを付け足していくわけです。

こうした付け足しのコードが積み重なると、最終的に使われる宣言がどれなのかをコード上で判断するのがとても難しくなります。特に空白は部分的に調整したくなってしまうものなので、空白を調整しようとすると、メディアクエリーによる上書きが増えてしまいます。

空白に限らず、メディアクエリーは減らせるに越したことはありません。

vwとclamp()で幅に応じた値を出す

もともとやりたかったことは、CSSの出し分けではなく、幅に応じたよい大きさの空白を設けることです。

幅に応じた値として、CSSにvwという単位があります。ウインドウ幅の100分の1を表す単位で、たとえばウインドウ幅が1200pxなら、1vwは12px相当になります。これを使えば、ウインドウ幅に応じて伸び縮みする値を作れます。

ウインドウ幅に対する割合の長さの単位について

vwを含めた、長さの単位については、次の記事にも詳細があります。

しかし、今回作りたいのは、最小52px、最大136px、その間は幅に応じて伸び縮みする空白です。単にvwを使うだけでは、最小と最大の値を決められません。なにかしらの工夫が必要です。

上限と下限を指定する関数として、clamp()という関数がCSSに導入されました。これはcalc()の一種で、中に指定した変化する値に、上限と下限を設けられるものです。

たとえば、次のコードでは、基本的な幅を70vwとしながら、ウインドウの幅が狭まった場合は下限が200px、広がった場合は上限が800pxとなるようにしています。

clamp()による上限/下限の設定

main {
  width: clamp(200px, 70vw, 800px);
  margin: 0 auto;
}

clamp()について

clamp()については、最小・最大の値を返すmin()/max()とともに別のシリーズで詳しく取り上げています。気になる方はそちらもあわせてお読みください。

vwclamp()を使えば、幅に応じて大きさを変えつつ、上限と下限のある空白が作れそうです。

ちょうどいい傾きのためにcalc()を使う

今回の例では、最小値が52px、最大値が136pxとわかっています。まずはわかる分だけ書いてみましょう。

最小値を52px、最大値を136pxに設定

section + section {
  margin-top: clamp(52px, /* 変化する値 */, 136px);
}

変化する値のところは、幅に応じて変化させたいのでvwを使うことになります。しかしvwそのままではうまく都合がつきません。たとえば10vwを指定した場合、幅1000pxでは高さが100pxになり、136pxには足りません。かといって20vwにしてしまうと、幅360pxでは高さが72pxになり、52pxをゆうに超えてしまいます。

52pxから136pxの間をちょうどいい感じにつなげるため、vwを調整してあげないといけません。CSSにはcalc()という関数があるので、これを使えばvwを使ったちょうどいい感じの値を計算できます。

calc()を使って変化する値を計算

section + section {
  margin-top: clamp(52px, calc(/* vwを使った計算 */), 136px);
}

calc()について

calc()の基本的な使い方については、次の記事もあります。気になる方はそちらもあわせてお読みください。

calc()に書くのは計算式

calc()の中に書くものは、幅が増えるに従って高さも増える値を返す計算式です。y=ax+bという、中学の数学で出てきた1次関数の式を覚えているでしょうか? あれです。

calc()に指定する式も同様にax + bのかたちをとります。今回のケースを当てはめると、xはvwの値になるので、CSSではcalc(a * 1vw + b * 1px)のように書くことになります。

calc()に計算式を入力する

section + section {
  margin-top: clamp(52px, calc(a * 1vw + b * 1px), 136px);
}

計算式の傾きと切片を求める

では、傾きaと切片bを求めましょう。aとbを求めるのに必要なのは、最小の高さと最大の高さ、そしてそれぞれの値に切り替わるブレークポイントです。

ピクセルグリッドのサイトにはブレークポイントがいくつかあります。今回はその中から、幅360pxと幅1024pxを採用しました。幅360pxから幅1024pxの間で、52pxから136pxに変化していく式を作ります。

xに入るのは、vwの値です。幅360pxにおいて、1vwは3.6pxとなります。幅1024pxにおいては、1vwが10.24pxとなります。yに入るのは高さです。高さは、幅360pxのときに52px、幅1024pxのときに136pxです。

これらをy=ax+bの式に当てはめましょう。

 52 = 3.6 × a + b
136 = 10.24 × a + b

2つの式ができました。連立方程式です。あとは、aとbを求めます。

まず傾きaを求めます。20年以上も前の中学のときを思い出しながら、筆者は紙とペンで計算しました。

     52 − 3.6 × a = 136 − 10.24 × a
(10.24 − 3.6) × a = 136 − 52
                a = 100 × (136 − 52) / (10.24 − 3.6)
                a = 12.650...

aは12.65くらいになりました。そのまま使ってもいいですが、そこまで厳密にしても仕方がないので、13を使います。

aが13のとき、bは5.2になりました。こちらも細かな値は使わず、5にします。

a=13、b=5なので、clamp()の第2引数の式はcalc(13vw + 5px)となります。

求めた傾きと切片bをcalc()に入力する

section + section {
  margin-top: clamp(52px, calc(13vw + 5px), 136px);
}

完成です。スマートフォンでみたときの空白がいい感じになりました。

ピクセルグリッドの業務のページで使っているので、幅の変化などを体感してみてください。

コードに計算を落とし込む

これで、メディアクエリーを使わず、さらに幅に応じて柔軟に変化する空白が表現できました。

しかし毎回aとbを求めるために手で計算するのはとても面倒です。数学に苦手意識を持つひとは、先ほどのセクションにちょっとうんざりしてしまったのではないでしょうか。

うれしいことに、計算はコンピューターが得意とするものです。ですので、手で計算していたものをコードに落とし込んでみましょう。ピクセルグリッドのサイトではSassを使っているので、calc()に書く値を自動的に計算し、clamp()に入れた空白の指定を書き出すmixinを定義しました。

空白を書き出すmixinの定義

// 幅に応じて伸び縮みする空白
// $property 値を指定するプロパティ
// $bpLower ブレークポイント(小)
// $bpUpper ブレークポイント(大)
// $spMin 値の下限
// $spMax 値の上限
@mixin responsiveSpacing($property, $bpLower, $bpUpper, $spMin, $spMax) {
  $a: round(100 * ($spMax - $spMin) / ($bpUpper - $bpLower));
  $b: round($spMin - ($bpLower / 100) * $a);
  #{$property}: $spMin; // clamp()に対応してないブラウザ用
  #{$property}: clamp(#{$spMin}, calc(#{$a}vw + #{$b}), #{$spMax});
}

あとは、使いたいところでmixinを呼び出します。

利用する場面でmixinを呼び出す

section + section {
  @import responsiveSpacing(
    "margin-top",
    $bp-narrow, $bp-wide,
    52px,       136px
  );
}

また、カスタムプロパティを駆使すれば、CSSだけでも同等のことはできるでしょう。

CSSだけで伸び縮みする空白もできる

:root {
  --sp-min: 52;
  --sp-max: 136;
  --bp-lower: 360;
  --bp-upper: 1024;
}

section + section {
  --a: calc(100 * (var(--sp-max) - var(--sp-min)) / (var(--bp-upper) - var(--bp-lower)));
  --b: calc(var(--sp-min) - (var(--bp-lower) / 100) * var(--a));
  margin-top: clamp(var(--sp-min) * 1px, var(--a) * 1vw + var(--b) * 1px, var(--sp-max) * 1px);
}

どういった場合に使うとよいのか

今回はレスポンシブな空白を作る方法として、clamp()vwcalc()の組み合わせを紹介しました。メディアクエリーの部分的上書きでない、幅に応じて伸び縮みするなど、おもしろく便利な仕組みかなと思っています。

ではページすべての空白にこれを使うべきかというと、そうは思っていません。

やはり、コードをパッと見たときのとっつきにくさは否めません。メディアクエリーとは異なり値の指定が分かれませんが、替わりにclamp()の中を読み解かないといけません。中に指定したcalc()の式だけを見ても、実際何ピクセルになるのかをすぐ求めるのは難しいでしょう。

今回筆者がこの方法を使ったのは、以前より試してみたかったという思いもありましたが、幅によって大きく変化する空白があり、そこになら効果的に使えると思ったからです。

裏を返せば、値が大きく変化しないパターンについては、とくに使う必要はないと思っています。20px―32pxをじわじわと変化させるために計算式を書くのは仰々しいと感じてしまうからです。

変化を付ける前に考えること

ピクセルグリッドのウェブサイトでは、伸び縮みさせるパターンは52px―84pxと52px―136pxの2つにしぼっています。空白が十分に変化するパターンを決め、むやみに伸び縮みさせないようにしました。

また、今回紹介したmixinも、使いすぎるとパッと見でわからないコードが散在してしまうおそれがあります。空白の見直しにあたり、ピクセルグリッドのウェブサイトでは空白をユーティリティクラスで管理することにしました。空白の調整箇所をCSSのコード上でも減らすことで、複雑さを減らすようにしています。

空白はどうしても調整したくなってしまうものです。しかし、空白の大きさやブレークポイントのパターンが増えてしまうと、管理できるものではなくなってしまいます。

デザイントークンを定義し使う値を減らす、スマホとPCで値を変えるようなことはしないなど、デザインの段階で空白を固めていく。変化をつける前に、まず空白を調整する機会を減らすのがよいでしょう。

おわりに

この仕組みは筆者が見つけたものではありません。以前より“CSS locks”と呼ばれ、紹介されています。“lock”は運河の閘門(こうもん)からきているようです。

コードではvwを使いましたが、もちろんvhも使えます。空白だけでなく、文字の大きさにも応用できます。変化する箇所の式も、2次関数にだってできるでしょう。みなさんもいろいろ遊んで、おもしろい使い方を見つけてみてください。