HTML5
CSS3
scss
マークアップ
フロントエンド

汎用性の高いCSSフレームワークを作ろう。#1 .container、レスポンシブ系CSS編

More than 1 year has passed since last update.
# やりたいこと
- 汎用性の高い .container クラスを作る
- メディアクエリをいい感じに管理する

Webサイトのコーディングを頻繁にやってると、毎回最初に同じようなCSSを書いている自分に気づきますね。
そろそろ自分用のライブラリでも作って、サイト制作のスタートダッシュを速くしたいという気持ちになってきました。

そんなわけで、 一度最強のCSSを書いてこれから未来永劫一生使い回していこうプロジェクトの、今回は映えある第一弾となります。

今日は、レイアウト系CSSの代表格、container とレスポンシブ周りのCSSを作っていこうと思います。

何はともあれ .container

まあよく見かけるのはこんな感じのcontainerではないでしょうか? bootstrap や bulma なんかもこんな感じですね。

.container {
 margin: 0 auto;
 width: 1200px;
 padding: 0 15px;
}

値を変数化していく。

これをいろんなプロジェクトで再利用可能なように、値を変数化していきます。

$container-padding: 15px;

$contents-width-xl: 1480px;

.container {
 margin: 0 auto;
 width: $contents-width-xl;
 padding: 0 $container-padding;
}

.containerの横幅をレスポンシブに

出でよ、メディアクエリィィィィィィィ

$container-padding: 15px;

$contents-width-xs: 400px;
$contents-width-sm: 768px;
$contents-width-md: 920px;
$contents-width-lg: 1272px;
$contents-width-xl: 1480px;


.container {
  margin: 0 auto;
  width: 1200px;
  padding: 0 $container-padding;

  @media screen and (max-width: calc(399px)) {
    width: 100%; } 
  @media screen and (min-width: $contents-width-xs) {
    width: $contents-width-xs; }
  @media screen and (min-width: $contents-width-sm) {
    width: $contents-width-sm; }
  @media screen and (min-width: $contents-width-md) {
    width: $contents-width-md; }
  @media screen and (min-width: $contents-width-lg) {
    width: $contents-width-lg; }
  @media screen and (min-width: $contents-width-xl) {
    width: $contents-width-xl; }
}
  padding: 0 $container-padding;

  box-sizing: border-box; 
        /* box-sizing は resetの段階で全要素に当てています。 
     最近 Bootstrap4 の reboot.css がこの宣言を全要素に対して適用させましたね。つけておいて問題は無いでしょう。 */

}

screen の幅が768px より大きくなったら、.container を 768px にして、、という要領ですね。
今回box-sizing: border-box; としたのは、デフォルトの box-sizing: content-box; だと、media-queryに入れる値とwidthに入れる値が左右のpaddingの分だけズレたりしてコードが見にくくなりそうなためです。

出でよ!とか言いましたが、@media screen and (max-width: 1000px) { } とか正直クソ長いし毎回書いてられないですよね。
media-query 略して mq としてmixin化してしまいましょう。
参考: Sassの変数とmixinで変更に強いメディアクエリをつくる

mixin を使って崩壊しないメディアクエリにしよう。

いい感じになってきた気がしますが、開発が進むうちに、ある日メディアクエリが人によってズレてることに気づくわけですね...
min-width の時はブレイクポイントが 768px だから、max-width: の時は 767px だよね♪ みたいなことは最初に書いた本人ですら1時間後には忘れてしまうのです。スクリーン幅としてありがちな1200px付近などでこのような微妙なズレによるデザインの崩れが起きると末恐ろしいですね。想像もしたくありません。

mixinを使って、だれでもわかりやすくメディアクエリを呼び出せるようにしておきましょう。

最終的にCSSの宣言部分はこうなります、

.container {

  

  @include mq-down(xs) {
    width: 100%;
  }

  @include mq-up(xs) {
    width: $contents-width-xs;
  }

  @include mq-up(sm) {
    width: $contents-width-sm;
  }

  @include mq-up(md) {
    width: $contents-width-md;
  }

  @include mq-up(lg) {
    width: $contents-width-lg;
  }

  @include mq-up(xl) {
    width: $contents-width-xl;
  }
}

かなりスッキリしましたね。
なにやら謎の mixin に対して謎の引数を渡していますが。
これの正体は、map で定義するやつのあれです。

このへんからすごくSCSSっぽくなってくるので、気合い入れていきましょう。
まず、SCSS のmapを使って、

$map-breakpoint-up: (
  'xs': 'screen and (min-width: #{$contents-width-xs})',
  'sm': 'screen and (min-width: #{$contents-width-sm})',
  'md': 'screen and (min-width: #{$contents-width-md})',
  'lg': 'screen and (min-width: #{$contents-width-lg})',
  'xl': 'screen and (min-width: #{$contents-width-xl})',
  ) !default;

$map-breakpoint-down: (
  'xs': 'screen and (max-width: calc(#{$contents-width-xs} - 1px))',
  'sm': 'screen and (max-width: calc(#{$contents-width-sm} - 1px))',
  'md': 'screen and (max-width: calc(#{$contents-width-md} - 1px))',
  'lg': 'screen and (max-width: calc(#{$contents-width-lg} - 1px))',
  'xl': 'screen and (max-width: calc(#{$contents-width-xl} - 1px))',
) !default;

こうします。js のオブジェクトみたいですね。
hoge-up となっているものは、それより上の時、つまりスクリーン幅の下限の時です。( min-width: hoge; )
逆に hoge-down となっているものは、それより下の時、つまりスクリーン幅の上限の時です。( max-width: hoge; )

ここで演算をしておくことで、ブレイクポイントの1pxの足し算引き算をする必要は生涯ありません。

そしてマッピングしたブレイクポイントの値を、mixin から呼び出します。

@mixin mq-up($breakpoint: md) {
  /* デフォルトの場合はmdが入るようにしておきます */
  @media #{map-get($map-breakpoint-up, $breakpoint)} {
    @content;
  }
}

@mixin mq-down($breakpoint: md) {
  @media #{map-get($map-breakpoint-down, $breakpoint)} {
    @content;
  }
}

個人的にここを理解するのがちょっと時間がかかりました。
@mixin の引数として受け取っているもの( 最初に見た呼び出し部分の sm とか lg とか ) を、そのまま map-get の第二引数として渡します。

こうすることで、map-get() でマッピングした対応する値を呼び出せるのですが、式展開しているのがポイントですね。

そんなこんなで出来上がったSCSSのコードがこちらになります。

/* ブレイクポイントと連動している container の幅になる。 */
$contents-width-xs: 400px;
$contents-width-sm: 768px;
$contents-width-md: 920px;
$contents-width-lg: 1272px;
$contents-width-xl: 1480px;

$container-padding: 15px;

$map-breakpoint-up: (
  'xs': 'screen and (min-width: #{$contents-width-xs})',
  'sm': 'screen and (min-width: #{$contents-width-sm})',
  'md': 'screen and (min-width: #{$contents-width-md})',
  'lg': 'screen and (min-width: #{$contents-width-lg})',
  'xl': 'screen and (min-width: #{$contents-width-xl})',
  ) !default;

$map-breakpoint-down: (
  'xs': 'screen and (max-width: calc(#{$contents-width-xs} - 1px))',
  'sm': 'screen and (max-width: calc(#{$contents-width-sm} - 1px))',
  'md': 'screen and (max-width: calc(#{$contents-width-md} - 1px))',
  'lg': 'screen and (max-width: calc(#{$contents-width-lg} - 1px))',
  'xl': 'screen and (max-width: calc(#{$contents-width-xl} - 1px))',
) !default;

@mixin mq-up($breakpoint: md) {
  @media #{map-get($map-breakpoint-up, $breakpoint)} {
// $breakpoint の中には xs sm md lg xl のどれかが入っているので、map の中で対応している文字列(screen 云々の長ーいやつら)を返してくれます。 
    @content;
  }
}

@mixin mq-down($breakpoint: md) {
  @media #{map-get($map-breakpoint-down, $breakpoint)} {
    @content;
  }
}

.container {
  margin: 0 auto;
  width: 1200px;
  padding: 0 $container-padding;
  box-sizing: border-box; 

  @include mq-down(xs) {
    width: 100%;
  }

  @include mq-up(xs) {
    width: $contents-width-xs;
  }

  @include mq-up(sm) {
    width: $contents-width-sm;
  }

  @include mq-up(md) {
    width: $contents-width-md;
  }

  @include mq-up(lg) {
    width: $contents-width-lg;
  }

  @include mq-up(xl) {
    width: $contents-width-xl;
  }
}

ふむ。
これで、 .container というクラスを当てればレスポンシブな中央寄せコンテンツができるようになりましたね。

補助的なクラスを作り、.containerをさらに柔軟に。

.container {

  (中略)

  &.is-lg-width {
    max-width: $contents-width-lg;
  }

  &.is-md-width {
    max-width: $contents-width-md;
  }

  &.is-sm-width {
    max-width: $contents-width-sm;
  }

  &.is-xs-width {
    max-width: $contents-width-xs;
  }
}

.container と一緒に使うクラスを用意します。
「大きい画面で見た時もそこまで大きくなってほしく無いタイプのcontainer」に対して、max-width を設定しましょう。
LPなど、縦長のサイトにありがちなやつですね。

そして、HTMLを以下のようにすると、、、

  <div class="container">
    普通のcontainer
  </div>
  <div class="container is-lg-width">
    これは最大が lg 幅
  </div>
  <div class="container is-md-width">
    これは最大が md 幅
  </div>
  <div class="container is-sm-width">
    これは最大が sm 幅
  </div>
  <div class="container is-xs-width">
    これは最大が xs 幅
  </div>

こうなります。
Screenshot from Gyazo

最終的なSCSS

今回書いたものをまとめると、こうなります。
実数値が、.contaienrのデフォルトの横幅を除くと一番上の6個しかありませんね。
最初にここだけ設定すれば、あとは何も恐れずに containerの中にコンテンツを書いていくだけです(きっと)。

/* ブレイクポイントと連動している container の幅になる。 */
$contents-width-xs: 400px;
$contents-width-sm: 768px;
$contents-width-md: 920px;
$contents-width-lg: 1272px;
$contents-width-xl: 1480px;

$container-padding: 15px;

$map-breakpoint-up: (
  'xs': 'screen and (min-width: #{$contents-width-xs})',
  'sm': 'screen and (min-width: #{$contents-width-sm})',
  'md': 'screen and (min-width: #{$contents-width-md})',
  'lg': 'screen and (min-width: #{$contents-width-lg})',
  'xl': 'screen and (min-width: #{$contents-width-xl})',
  ) !default;

$map-breakpoint-down: (
  'xs': 'screen and (max-width: calc(#{$contents-width-xs} - 1px))',
  'sm': 'screen and (max-width: calc(#{$contents-width-sm} - 1px))',
  'md': 'screen and (max-width: calc(#{$contents-width-md} - 1px))',
  'lg': 'screen and (max-width: calc(#{$contents-width-lg} - 1px))',
  'xl': 'screen and (max-width: calc(#{$contents-width-xl} - 1px))',
) !default;

@mixin mq-up($breakpoint: md) {
  @media #{map-get($map-breakpoint-up, $breakpoint)} {
    @content;
  }
}

@mixin mq-down($breakpoint: md) {
  @media #{map-get($map-breakpoint-down, $breakpoint)} {
    @content;
  }
}

.container {
  margin: 0 auto;
  width: 1200px;
  padding: 0 $container-padding;
  box-sizing: border-box; 

  @include mq-down(xs) {
    width: 100%;
  }

  @include mq-up(xs) {
    width: $contents-width-xs;
  }

  @include mq-up(sm) {
    width: $contents-width-sm;
  }

  @include mq-up(md) {
    width: $contents-width-md;
  }

  @include mq-up(lg) {
    width: $contents-width-lg;
  }

  @include mq-up(xl) {
    width: $contents-width-xl;
  }

  &.is-lg-width {
    max-width: $contents-width-lg;
  }

  &.is-md-width {
    max-width: $contents-width-md;
  }

  &.is-sm-width {
    max-width: $contents-width-sm;
  }

  &.is-xs-width {
    max-width: $contents-width-xs;
  }
}

いかがでしょうか?
たかだかcontainerごときでくそ長いですね。本当に長いです。

がしかし!これを書いておくことで、コーディングするたびに.container を最初にちまちま書いたり、メディアクエリが崩壊することはもうありません。 最初に欲しい数だけブレイクポイントを設定するだけです。

実際コンパイルされて出てくるCSSはたったこれだけになりますが、一度書いてしまうとなかなか便利なので、ぜひ参考にしてみてください。

.container {
  margin: 0 auto;
  padding: 0 15px;
  overflow: hidden;
}
  .container.is-lg-width {
    max-width: 1272px; }
  .container.is-md-width {
    max-width: 920px; }
  .container.is-sm-width {
    max-width: 768px; }
  .container.is-xs-width {
    max-width: 400px; }
  @media screen and (max-width: calc(400px - 1px)) {
    .container {
      width: 100%; } }
  @media screen and (min-width: 400px) {
    .container {
      width: 400px; } }
  @media screen and (min-width: 768px) {
    .container {
      width: 768px; } }
  @media screen and (min-width: 920px) {
    .container {
      width: 920px; } }
  @media screen and (min-width: 1272px) {
    .container {
      width: 1272px; } }
  @media screen and (min-width: 1480px) {
    .container {
      width: 1480px; } }

おわりに

最後まで読んでいただき、ありがとうございます。
これからちょっとずつ自分CSSライブラリを作っていくので、また進捗報告がてら書くことにします。

まだまだひよっこなので、ソースコードに関するご意見ご感想、お待ちしております。

次回はテキスト周りのCSS初期設定について。