🖥️

"いい感じに見える"レスポンシブデザイン実装

2023/03/12に公開

社内向けにまとめていたものを公開します。cssメタ言語と、レスポンシブデザインをある程度経験したことがある方向けの記事です。

挙動はこちらで確認できます。
https://minagawa161.github.io/responsive-design/
リポジトリ
https://github.com/minagawa161/responsive-design

目的

  • エンジニアのスキルによる品質のばらつきを解消したい
  • 実装の工数を減らしたい

前提

弊社では、横幅384pxのモバイル用のデザインと、横幅1280pxのデスクトップ用のデザインが共有され、それらをもとにページを実装します。
2つは異なるデザインのため線形補間ができないので、横幅384pxと横幅1280px以外でどんな表示になるかはエンジニアに実質委ねられていました。デザイナーとコミュニケーションを取ればどんな表示にするか認識をある程度すり合わせることが出来ますが、時間が限られている中で1ページずつ細かいところまですり合わせるのは時間がかかり過ぎてしまうからです。
そのため、デザイナーと大まかな方針を決めたら、エンジニアは"いい感じに見える"実装をしてしまうことがほとんどでした。ただ、このやり方ではエンジニアによって感覚が異なることから、品質にばらつきが生じてしまうのが問題となっていました。

どうすれば解消できるか

"いい感じに見える"表示をデザイナーとあらかじめすり合わせておき、それを守れるシンプルな実装方法をエンジニアに提供できれば、ある程度の品質が保たれた成果物ができる、と考えました。
下記の例のように、デザインデータ通りの数値をいれるとレスポンシブデザインが考慮されたページが出力されるようになれば実装はシンプルで楽になるので、そんなルールを目指すことにしました。

// モバイルファースト
.el {
  width: length(100); // モバイル用の記述
  @include mq {
    width: length-md(100); // デスクトップ用の記述
  }
}

実現したい挙動

デザイナーとすり合わせた挙動が以下になります。

モバイル用のデザイン

モバイルの横向きに対応するため、vmin相当の値を使用します。

– 575px

vmin相当の値で実装します。

576px(384pxの1.5倍) – 959px

デザインを1.5倍まで拡大したもので固定します。拡大し続けると要素が大きすぎる印象を受けるからです。
コンテンツ幅のみ横幅に応じて変わり、コンテンツ幅によって変わる要素は固定しません。

デスクトップ用のデザイン

vw相当の値を使用します。

960px(1280pxの0.75倍) – 1599px

vw相当の値で実装します。
font-sizeの値のみ87.5% – 112.5%で拡大・縮小させます。等倍で拡大・縮小すると読みづらくなるからです。
font-sizeの値は等倍で小さくならないので、960pxでデザインが崩れていないか注意して表示確認しましょう。

1600px(1280pxの1.25倍) –

デザインを1.25倍まで拡大したもので固定します。

挙動を実現する関数

準備

vw問題

vwはスクロールバーの横幅を含む値です。デザインと少しずつずれてしまうので、スクロールバーの横幅を含まない値をcss変数に格納しておきます。

const vw = document.body.clientWidth * 0.01;
document.documentElement.style.setProperty("--vw", `${vw}px`);
// リサイズ処理は省略

ユーティリティ

$mobile: 384px;
$desktop: 1280px;
$medium: 960px; // モバイル用のデザインとデスクトップ用のデザインが切り替わる横幅

// 単位を取り除く関数
// https://css-tricks.com/snippets/sass/strip-unit-function/
@function strip-unit($number) {
  @if type-of($number) == "number" and not unitless($number) {
    @return math.div($number, ($number * 0 + 1));
  }
  @return $number;
}

// 基本的には等倍で拡大縮小したいので、デザインの横幅で値を割る
@function vw($number, $width: $mobile) {
  @return calc(var(--vw, 1vw) * #{strip-unit($number)} / #{strip-unit($width)} * 100);
}
@function vmin($number, $width: $mobile) {
  @return calc(min(var(--vw, 1vw), 1svh) * #{strip-unit($number)} / #{strip-unit($width)} * 100);
}

// Media Query
@mixin mq($breakpoint: $medium) {
  @media screen and (min-width: $breakpoint) {
    @content;
  }
}

モバイル用の関数

// デザインを1.5倍まで拡大したもので固定する
@function length($number) {
  $number: strip-unit($number);
  @return min(vmin($number), $number * 1.5 * 1px);
}

// font-sizeにはremを使いたいので、別途関数を用意する
// https://css-tricks.com/simplified-fluid-typography/
@function font-length($length, $width: $mobile) {
  $length: strip-unit($length);
  @return min(
    calc(#{$length * 0.1rem} + (min(var(--vw, 1vw), 1svh) * 100 - #{$width}) * #{math.div($length, strip-unit($width))}),
    #{$length * 1.5 * 0.1}rem
  );
}
// ほとんどfont-sizeプロパティで使うので、mixinも作成
@mixin font-size($length, $width: $mobile) {
  font-size: font-length($length, $width);
}

デスクトップ用の関数

// デザインを1.25倍まで拡大したもので固定する
@function length-md($number) {
  $number: strip-unit($number);
  @return min(vw($number, $desktop), $number * 1.25 * 1px);
}

// font-size
// 87.5% – 112.5%で拡大・縮小
// $scaleで倍率を調整する
@function font-length-md($length, $scale: 0.25 * 0.5) {
  $length: strip-unit($length);
  @return min(
    calc(#{$length * 0.1rem} + #{$length * $scale} * (var(--vw, 1vw) * 100 - #{$desktop}) / #{strip-unit($desktop * 0.25)}),
    #{($length * ($scale + 1)) * 0.1rem}
  );
}
@mixin font-size-md($length, $scale: 0.25 * 0.5) {
  font-size: font-length-md($length, $scale);
}

実装例

関数が作成できたので、以下に使用例を掲載します。

基本

// 長さを指定するときは基本的にはlength($number) と length-md($number)を使う
.el-1 {
  width: length(100);
  @include mq {
    width: length-md(100);
  }
}

// font-sizeプロパティの値を指定するときは、
// @include font-size($number); と @include font-size-md($number);を使う

// font-sizeプロパティ以外でfont-sizeプロパティに応じて可変するものを作りたいときは、
// font-length($number) と font-length-md($number)を使う
.el-2 {
  @include font-size(100);
  width: font-length(100);

  @include mq {
    @include font-size-md(100);
    width: font-length-md(100);
  }
}

応用

負の値を使いたい

.el {
  margin: calc(#{length(100)} * -1);
}

画面幅いっぱいに背景を伸ばしたい

.el {
  margin: 0 calc(50% - var(--vw, 1vw) * 50);
  padding: 0 calc(var(--vw, 1vw) * 50 - 50%);
}

デスクトップ用のデザインで、画面幅が狭いときはfont-sizeの値を等倍で拡大縮小したいけど、途中から倍率を戻したい

.el {
  @include mq {
    @include font-size-md(100, 0.25);
  }
  @include mq($large) {
    @include font-size-md(100);
  }
}

デバッグ

macOS Chromeを想定しています。
横幅 384px(Mobile), 960px(Desktop), 1280px + 15px(Desktop)で確認したいときが多いので、カスタムデバイスとして登録しておくことをおすすめします。
※15pxはmacOSのスクロールバーの大きさ
https://willcloud.jp/knowhow/dev-tools-01/#i-8:~:text=ができます。-,新しい端末の追加方法,-デバイス選択メニュー

終わりに

弊社でこのルールを導入してしばらく経ちますが、表示に対する社内の評判はよく、これといって問題点も出てきていません。機械的に実装できるので他の作業に時間が掛けられるようになったとエンジニアにも好評でした。

おまけ

VSCodeのスニペットを乗せておきます。

{
  "length": {
    "prefix": "length",
    "body": ["length($1)"]
  },
  "length-md": {
    "prefix": "length-md",
    "body": ["length-md($1)"]
  },
  "font-size": {
    "prefix": "font-size",
    "body": ["@include font-size($1);"]
  },
  "font-size-md": {
    "prefix": "font-size-md",
    "body": ["@include font-size-md($1);"]
  },
  "font-length": {
    "prefix": "font-length",
    "body": ["font-length($1)"]
  },
  "font-length-md": {
    "prefix": "font-length-md",
    "body": ["font-length-md($1)"]
  },
  "Media Query": {
    "prefix": "mq",
    "body": ["@include mq {", "  $1", "}"],
  },
  "画面幅いっぱいに背景を伸ばす": {
    "prefix": "m0c",
    "body": [
      "margin: 0 calc(50% - var(--vw, 1vw) * 50);",
      "padding: 0 calc(var(--vw, 1vw) * 50 - 50%);"
    ],
  }
}

Discussion