レスポンシブなサイズ計算を考える

  • プロダクション
レスポンシブなサイズ計算を考える

目次

複雑化するサイズ指定を簡単にしたい

レスポンシブデザインもすっかり一般化し、それにつれてデザイン要件はより柔軟に、より複雑になってきています。そうしたニーズに呼応するように、CSSも日進月歩で新しい機能が追加され続けています。

本稿では、レスポンシブコーディングにおいてとかく複雑化しがちなサイズ計算を、新しいCSSの機能なども援用しながら、なるべく簡単でシンプルになるように知恵を絞っていきたいと思います。

与件

  • h1見出しのフォントサイズを、スマホでは20pxとせよ
  • 同じくPCでは30pxとせよ
  • その中間の解像度は、なんかこう、イイ感じにせよ

このようなフンワリとした要件でコーディングをご依頼いただくこともあります。デザイナーやディレクターと協議して、より具体的な要件に落とし込みました。

  • 幅375px以下のビューポートでは20px固定
  • 幅1280px以上のビューポートでは30px固定
  • 幅376~1279pxの範囲では、20~30pxの間を連続可変

グラフにするとこうなります(模式図です)。

実装

連続可変するサイズの計算

ビューポートにつれてフォントサイズが連続可変する部分が厄介ですね。まずここをやっつけてしまいましょう。上のグラフで言うとこの部分です。

ハイライトした部分だけを抜き出してみると、ビューポートの幅が375pxから1280pxに増える間に、フォントサイズは20pxから30pxに増えています。それぞれ引き算して、ビューポートが905px増えるとフォントサイズが10px増えますので、ビューポートの増減量に対するフォントサイズの伸縮率は10:905の比例関係となっています。

現在のビューポート幅には100vwという相対値が使えますから、「現在のビューポートがブレイクポイントの375pxをいくら超えているか」はcalc()で計算できます。

calc( 100vw – 375px )

相対値や物理値や単位をチャンポンで計算できるのがcalc()の素晴らしいところですね。これに先ほどの比「10:905」を掛けてやれば「フォントサイズがいくら増えるか」が計算できます。

calc( (100vw – 375px) * 10 / 905 )

これに元のフォントサイズ(20px)を足すと、最終結果が出ます。

calc( (100vw – 375px) * 10 / 905 + 20px)

【デモ】ビューポート連動calc()のデモ

上限値と下限値の設定

難関の可変部分が片付きました。が、このままでは比例式をcalc()に突っ込んだだけですので、フォントサイズはまだ上下に無限に可変していく状態です。

スマホ以下では20px固定、PC以上では30px固定というのが要件ですので、メディアクエリーで範囲を整えていきましょう。

Media Queries Level 4

ビューポートに応じたスタイルを適用する場合、通常はメディアクエリーを利用します。従来はこのような書き方が一般的でした。

/* ビューポート幅 376px 以上 1279px 以下に有効 */
@media (min-width: 376px) and (max-width: 1279px) {
    h1 {
        font-size: calc((100vw - 375px) * 10 / 905 + 20px);
    }
}

Media Queries Level 4 では、新たに Range Syntax という記法が追加されました。以下のように記述することができます。

/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
}
 
/* ビューポート幅 375px 以下に有効 */
@media (width <= 375px) {
    h1 {
        font-size: 20px;
    }
}
 
/* ビューポート幅 375px 超~ 1280px 未満に有効 */
@media (375px < width < 1280px) {
    h1 {
        font-size: calc((100vw - 375px) * 10 / 905 + 20px);
    }
}
 
/* ビューポート幅 1280px 以上に有効 */
@media (1280px <= width ) {
    h1 {
        font-size: 30px;
    }
}

min-○○ や max-○○ ではなく等号不等号を用いて記述するのでとても直感的ですね。また、数値を変えないまま「以下」と「未満」とを書き分けられるので、CSSプリプロセッサなどではブレイクポイントを変数化しやすいでしょう(ただし、ピュアCSSではメディアクエリーの条件式にCSS変数を使うことはできません。使えればいいのに!)。

【デモ】ビューポート連動calc()+メディアクエリーのデモ

【参考】Can I Use – Media Queries: Range Syntax

clamp()関数の利用

次に、メディアクエリーを使わない方法をご紹介します。メディアクエリーの説明がただの字数稼ぎに見えてしまうくらい便利です。clamp()という新しいCSS関数を使います。

clamp()関数では、計算値に下限と上限を設定して、値の範囲を制限することができます。以下のように記述します。

CSSプロパティ: clamp( 下限値, 計算式, 上限値 );

このclamp()関数を用いて上のスタイルシートをリファクタリングしてみましょう。

/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
    font-size: clamp(20px, (100vw - 375px) * 10 / 905 + 20px, 30px);
}

これですべてです。あまりにもあっけない記述になりました。メディアクエリーを用いなくとも、clamp()関数による値の制限がまるでプレイクポイントのように機能します。

【デモ】ビューポート連動clamp()関数のデモ

(おまけ)もっと一般化したい

(注:ここから先は遊びです)

clamp()関数の中に複雑な式が入っていて邪魔なので、計算部分だけを変数として外に出してしまいましょう。もっとも、このまま使い回しできる式ではないので、clamp()関数の中がすっきりする程度の意味合いしかありません。

:root {
    --h1-fs: calc((100vw - 375px) * 10 / 905 + 20px);
}
 
/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
    font-size: clamp(20px, var(--h1-fs), 30px);
}

しかしこうなってくると、もっと変数を増やして抽象度を高めたい、計算式をより一般化したい、そう思うのがエンジニアの常。コードの中に固有の数字がハードコードされているとメンテナンスしにくくなりますし、複製してバリエーションを作るにも、どの数値を変更すればいいかわかりにくいです。

生(なま)の数値が入っている部分をすべて変数化してみたものがこちらになります(※以下のスタイルシートは機能しません)。

:root {
    --break-point-sp: 375px;
    --break-point-pc: 1280px;
    --min-h1-fs: 20px;
    --max-h1-fs: 30px;
    --h1-fs: clamp(
        var(--min-h1-fs),
        (100vw - var(--break-point-sp)) * (var(--max-h1-fs) - var(--min-h1-fs)) / (var(--break-point-pc) - var(--break-point-sp)) + var(--min-h1-fs),
        var(--max-h1-fs)
    )
}
 
/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
    font-size: var(--h1-fs);
}

残念ながらこのCSSは機能しません。計算式の中に、仕様上許されていない計算が含まれているからです。

上の例では、10:905というキモとなる膨張率を算出するために以下のような割り算をしています。

この式では「比」を求めようとしているので、値は分子・分母ともに「長さ」です。ところが、CSSの仕様では、割り算の分母は「数値」でなければならないと定めています。

【参考】MDN: calc()

つまりCSSの数学関数では、長さを長さで割って比を導くことができないのです。この仕様はわりと意外ですし、不便ですね。(まさに今回のように)長さ同士の比を計算したいケースもあるように思うのですが……。ちなみに掛け算も、少なくとも片方は数値でなければならないと定められています。が、これはまあ当然でしょう。

強引な解決策のひとつとして、すべての長さを「数値」で取り扱ってみます。数値同士であればいかようにも加減乗除が可能ですし、値に1pxを掛けることでデータ型をいつでも「長さ」に戻すことができます。そのようにして書き換えたスタイルがこちら。

:root {
    --break-point-sp: 375; /* 単位pxを省く*/
    --break-point-pc: 1280;
    --min-h1-fs: 20;
    --max-h1-fs: 30;
    --h1-fs: clamp(
        var(--min-h1-fs) * 1px, /* 単位pxが必要なところだけ 1px を掛ける */
        (100vw - var(--break-point-sp) * 1px) * (var(--max-h1-fs) - var(--min-h1-fs)) / (var(--break-point-pc) - var(--break-point-sp)) + var(--min-h1-fs) * 1px,
        var(--max-h1-fs) * 1px
    )
}
 
/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
    font-size: var(--h1-fs);
}

上記のコードは実際に機能しますが、むしろ冗長で不便になっていますね……。現実味のある落とし所は、問題の膨張率だけをハードコードしてしまうあたりでしょうか。

:root {
    --break-point: 375px;
    --min-h1-fs: 20px;
    --max-h1-fs: 30px;
    --ratio-h1-fs: calc((30 - 20) / (1280 - 375)); /* 実数値で計算 */
    --h1-fs: clamp(
        var(--min-h1-fs),
        (100vw - var(--break-point)) * var(--ratio-h1-fs) + var(--min-h1-fs),
        var(--max-h1-fs)
    )
}
 
/* 全体に有効 */
h1 {
    font-family: 'Verdana', sans-serif;
    font-size: var(--h1-fs);
}

ハードコードした変数 --ratio-h1-fs の値は自明なので、「0.01104972375」という計算結果をそのまま代入してしまってもよいのですが、意図的に式の形のまま残しています。式を残しておけばこのコードを改変する際に膨張率の算出手順がなぞれますし、いちいち電卓を叩く手間もはぶけます。

最後は興味本位のお遊びになってしまい、失礼しました。

【デモ】ビューポート連動clamp()関数一般化のデモ

この記事の執筆者

G・I

テクノロジーソリューション事業部

この記事に関するご相談やご質問など、お気軽にお問い合わせください。

お問い合わせ

タグ一覧