コンテンツ幅と画面いっぱいの背景とCSSカスタムプロパティ

コンテンツの幅を一回で決める

Webページを作る際、完全なリキッドでなければ、コンテンツの最大幅(例えば960pxとか1280pxとか:以下「コンテンツ幅」と呼びます)を設定しておくと捗ります。

CSSの実装方法として、一つはセクションやその内側の要素ごとに最大幅を指定するというという方法があります。

<article>
  <div class="content">
    <h1>走れメロス</h1>
    <p>メロスは激怒した。必ず、かの邪智暴虐の王を除かねばならぬと決意した。...</p>
    ...
  </div>
</article>
.content {
  max-width: 960px;
  margin: 0 auto;
}

ただこの場合、個々のブロックにクラスを指定したり余計な要素を追加したりする必要があり、変更がやや面倒です。

コンテンツ幅は余計なことをしなくても一発で決まるようにしたい……。

そこで、コンテンツ全体を覆う親要素に指定する方法がおすすめです。

<main>
  <article>
    <h1>走れメロス</h1>
    <p>メロスは激怒した。必ず、かの邪智暴虐の王を除かねばならぬと決意した。...</p>
    ...
  </article>
  <article>
    <h1>人間失格</h1>
    <p>私は、その男の写真を三葉、見たことがある。</p>
    ...
  </article>
</main>
main {
  max-width: 960px;
  margin: 0 auto;
}

これならあとから新しい要素を追加した場合も、なにも考えなくてもコンテンツ幅に収まります。

背景を塗りつぶしたい

このコンテンツ幅を親要素で一括指定する方法だと、背景を画面いっぱいに表示させたい場合に問題になります。前述の方法だと .content の親に背景を指定するだけで済むのですが、この方法だとそうもいきません。

そこで背景を引き伸ばしたいときはマイナスマージンを使います。

.expand {
  margin-right: calc(50% - 50vw);
  margin-left: calc(50% - 50vw);
  padding-right: calc(50vw - 50%);
  padding-right: calc(50vw - 50%);
}

Sassを使っているならmixinに登録しておくと便利です。

50% はコンテンツ幅の半分(例では480px)、50vw はウィンドウ幅の半分になるので、ウィンドウがコンテンツ幅より大きければマージンは負になり、はみ出します。そしてはみ出した分をパディングで埋めてやれば、コンテンツ幅を維持したまま背景を画面いっぱいに広げることが可能になります。

ところがこの方法では、スクロールバーがある場合に問題が起きます。vw がスクロールバー込みのウィンドウ幅で計算されるので、縦スクロールバーがあると、そのバーのところまではみ出して画面内に収まらなくなり、横スクロールしてしまいます。

なので、横方向のはみ出しは見えなくしてやりましょう。

body {
  overflow-x: hidden;
}

こうするとはみ出した分は表示されなくなるので、横にスクロールすることはなくなります。

See the Pen
Expand background width
by SHIN Inc. (@shin-inc)
on CodePen.0

oveflow-x:hidden を使わずに塗りつぶす

もし何らかの理由で 'overflow-x:hidden' を使いたくない・使えないという場合は、スクロールバーの幅をマージンに加味してあげる必要があります。

でもスクロールバーの幅なんて知りませんし、環境によって幅が違ったり、表示されないことだってあります。決め打ちではどうしようもありません。

これを解決するには、CSSカスタムプロパティとJSを組み合わせる方法があります。カスタムプロパティとは、CSSで「変数」を扱う仕組みです。現在の主流ブラウザなら問題なく使用できます。

例えば以下のように使います。

.box {
  --lh: 1.5;
  height: calc(var(--lh) * 8em); // 8行分の高さで固定
  line-height: var(--lh);
  overflow: auto;
}

カスタムプロパティは --lh のようにハイフン二つで始まる名前に、特定のプロパティ値(ここでは1.5)を与えて宣言します。値を読み出すには var() を使います。

このカスタムプロパティを使って、以下のようにCSSを設定します。

:root {
  --scrollbar: 0px;
}
.expand {
  margin-right: calc(50% - (100vw - var(--scrollbar)) / 2);
  margin-left: calc(50% - (100vw - var(--scrollbar)) / 2);
  padding-right: calc((100vw - var(--scrollbar)) / 2 - 50%);
  padding-left: calc((100vw - var(--scrollbar)) / 2 - 50%);
}

50vw の代わりに、100vw からスクロールバーの幅を引いたものの半分を計算に使うわけです。

スクロールバーの幅自体はJSで計算して取得します。

const scrollbar = window.innerWidth - document.documentElement.clientWidth + 'px';
document.documentElement.style.setProperty('--scrollbar', scrollbar);

ウィンドウの内側の幅からHTML要素の幅を引けば、それがスクロールバーの幅です。それをルート要素(HTML要素)にカスタムプロパティとして与えてやれば、あとはCSSが勝手にやってくれます。カスタムプロパティに対応していないブラウザでは無効ですが、単に背景が引き伸ばされないだけなので、閲覧にはなんの問題もありません

あまり使う機会はないかもしれませんが、この方法なら「スクロールバーの幅」をCSS側から自由に扱えるようになります。

スマホではみ出る問題

本筋とはあまり関係ありませんが、スマホだと 'body' に 'overflow-x:hidden' を指定しても効きません。左側にはみ出たものは見えなくなりますが、右側にはみ出ると横スクロールします。

スマホのブラウザは基本的にスクロールバーが表示されないので、特に問題ありませんが、もし表示されるブラウザがあったらはみ出ちゃうので、上記のカスタムプロパティを使った方法が有用になるかもしれません。内側の要素に 'overflow-x:hidden' を指定すればいい話でもあるのですが。