Chienomi

はやわかり レスポンシブルCSS

プログラミング

まえがき

リクエストがあったので、レスポンシブルウェブサイトを構築するCSSの基本的な考え方を簡単にまとめよう。

これは、CSSやHTML自体を書けない人を対象にしたものでは ない

ケース分けの仕方

CSSのメディアクエリを使用する。 メディアクエリの詳細についてはMDNに記事がある

ほとんどの場合メディア特性にはwidthまたはheightを使用する。 広く使われてはいないが、aspect-ratio, orientationは基本的なメディア特性であり、非常に有用である。 この両者を組み合わせることで(ゲーム画面のように)スクリーンサイズに合致するレイアウトが可能になる。

典型的なケースでは、レイアウトボックスが1000px幅だとして、ビューポートに1000pxがなければ画面いっぱいにfallbackする。

@media screen and (min-width: 1000px) {
  #MainBox {
    width: 1000px;
    margin: auto;
  }
}
@media screen and (max-width: 999px) {
  #MainBox {
    width: 100%;
    margin: 0px;
  }
}

ただし、このようなケースでは次のように書くべきだ。 そして、実際にこのように書くべきケースは非常に多く、メディアクエリを必要とするケースは表示切り替えくらいのものである。

#MainBox {
  width: 1000px;
  max-width: 100%;
  margin: auto;
}

この場合、#MainBoxは幅1000pxをまず確保しようとする。 しかし、min-widthによって制約されているため、1000pxの幅が包括ブロックの100%を越える場合は包括ブロックの100%に留められる。 これによってビューポート幅を突き破ることがないようにできる。

widthによって判定するのは「コンテナの中央寄せ」という、2003年以来の文法に従っているためだ。 だから、しっかりとデザインするのであればまた異なった設定になるだろう。

HTML構造

基本的なレイアウトでは全体を収めるためのボックスを用意する。

<body>
  <section id="MainBox">
  </section>
</body>

この中にレイアウトしていくのだが、基本的には「左であり、上である」「右であり、下である」という順序で書く。 これを入れ替えるのはCSSでは少し難しい。 ただ、良いCSSを書くには「可能な限りJavaScriptに頼らない」という気持ちは必須である。

「本文コンテナとサイドバー」という構成であれば、サイドバーの内容を上にしたいのであれば左サイドバーになるし、サイドバーの内容を下にしたいのであれば右サイドバーになる。

<body>
  <section id="MainBox">
    <article id="MainContent">
    </article>
    <nav id="SideBar">
    </nav>
  </section>
</body>

横並びレイアウトで最も基本的なのは「コンテナをテーブルに、コンテンツをセルに」である。 この場合、例え縮小しても横並びは維持されるし、min-widthなどではみ出す場合、そのままoverflowする。

#MainBox {
  display: table;
  margin: auto;
}
#MainContent {
  width: 800px;
  display: table-cell;
}
#SideBar {
  width: 300px;
  display: table-cell;
}

コンテナ側のサイズが決まっていて、サイドバーのサイズを指定しないことでサイドバーをある程度縮小させることを許すことができる。 これは、最低限必要な幅を確保した上で、それよりも幅があるのであればもう少しスペースをとって表示することができる。

次のCSSでは、1000pxを下回るのであればtableによる表示を諦めるが、ボックス自体は最大1200pxまで伸張する。 1200pxの場合、その割合としてはサイドバーが200pxから300pxの間で、残りがメインになる。

@media screen and (min-width: 1000px) {
  #MainBox {
    display: table;
    margin: auto;
    min-width: 1000px;
    max-width: 1200px;
  }
  #MainContent {
    min-width: 800px;
    display: table-cell;
  }
  #SideBar {
    min-width: 200px;
    max-width: 300px;
    display: table-cell;
  }
}

十分なスペースがないときに右のボックスを下に送るのであれば、メディアクエリは必要ない。 inline-blockとして配置することで、overflow時はボックスを並べないようにすることができる。

この場合、サイズ指定は並んだときに確保すべきボックスと、単独になったときに伸張すべきボックスの大きさを意識する必要がある。 また、内包されているボックスの位置と大きさはいずれも#MainBoxにbindされていることを忘れてはいけない。

#MainBox {
  width: 1450px;
  max-width: 100%;
  margin: auto;
}
#MainContent {
  min-width: 800px;
  max-width: 100%;
  display: inline-block;
}
#SideBar {
  min-width: 300px;
  max-width: 100%;
  display: inline-block;
}

表示ボックスそのものの変更

ボックスのレイアウトではなく、内容そのものをレスポンシブルに変更したい場合はdisplayを上手に使うといい。 例えば次の場合、#TOCは十分な幅がないとき省略される。

@media screen and (max-width: 999px) {
  #TOC { display: none; }
}

メニューは十分な幅があれば横に配列しようとするが、ないのであればそのまま縦に配列する。

@media screen and (min-width: 1000px) {
  #MainMenu li { display: inline-block; }
}

同一の内容に対して異なる表示を提供したい場合は、予め複数書いておくようにして、メディアクエリでdisplay: block;display: noneを入れ替えるのが良い。 この場合、同内容はジェネレータによって生成されるべきである。PureBuilder Simplyの場合、Pandocテンプレートを使うことにより内容の反復を避けることができる。 より一般的にはテンプレートそのものをeRubyなどで生成し、値を置き換えるのが良いだろう。

順序の変更も、「複数書いておいて、表示を切り替える」のが最も無難である。

がんばるのであれば、position: absoluteあるいはposition: fixedを使うことで「見かけ上の順序」をごまかすことができる。 これらはビューポートをそのまま使うブロックに対して指定することが多いためあまり意識しないだろうが、座標起点は包括ブロックであり、また包括ブロックとしても機能する。 以下の例では十分な幅があればサイドバーは左に表示されるが、十分な幅がないとき、サイドバーは下に表示される。

<body>
  <section id="MainBox">
    <nav id="SideBar">
    </nav>
    <article id="MainContent">
    </article>
  </section>
</body>
#MainBox {
  width: 1000px;
  max-width: 100%;
  margin: auto;
}

@media screen and (min-width: 1100px) {
  #SideBar {
    position: absolute;
    top: 0px;
    left: 0px;
    min-width: 300px;
    max-width: 300px;
  }

  #MainContent {
    position: absolute;
    top: 0px;
    right: 0px;
    min-width: 700px;
    max-width: 700px;
  }
}

表示コンテンツの変更

レスポンシブルに異なるバナーをコンテンツ中に表示したいような場合は、JavaScriptを利用するほうが良い。 ただし、メディアクエリと背景画像を使うことでCSSで処理できないこともない。

各ページ共通のものであればテンプレートに組み込んで表示ボックスの切り替えが良い。

表示コンテンツの表示の仕方でいえば、max-width, min-width, そしてmarginの値をコントロールするようにするといいだろう。

おまけ。ビューポートを全部使う

文字主体のコンテンツの場合、文字サイズをビューポートに基づくようにすれば問答無用でビューポートいっぱい使うデザインが可能。

#MainBox {
  max-width: 100%;
  margin: 0px;
  padding: 0px;
}
@media (orientation: portrait) {
  #MainContent {
    font-size: 5vw;
  }
}
@media (orientation: landscape) {
  #MainContent {
    font-size: 5vh;
  }
}

5vwは一応、1行に20文字入る計算になる。 この場合、横幅が小さい場合は文字数が、縦幅が小さい場合は行数がある程度確保されるようにするという基準になっている。 その上でなるべく大きい文字にする。画面いっぱい使って大きく文字を表示するのでお年寄りにもやさしい。

標準的に1920pxに対して16pxの文字とすると120文字入るので0.85vhとなるのだが、これをすると非常に小さくなってしまう。

これをやると拡大縮小がかなり制限されてしまうことから、「大きく文字を表示する」前提で考えたほうが良い。 2vhくらいあればちょっと大きめに表示されるが、拡大できないので2.5vhくらいをスタートに考えたほうが良いかもしれない。 特にウィンドウをタイルしたときなどで縦長になるとすごく小さい、などということもありえるのだ。