HTML5Experts.jp

理解しておきたい、CSSによるインラインレイアウトの仕組み(inline-block編)Inline Layout─Frontrend Conference

この記事は、Frontrend Conferenceのセッション「Inline layout」でお話させていただいた内容を基に、連載記事(全4回)として書き起こしたものです。今回は第4回目、最終回です。

背景画像でリストのビュレットを配置した場合の問題

今回は、前回までの内容を踏まえ、主にインラインブロックについて見ていきます。まずは、以下のようなリストを「HTMLとCSSで作りたい」と思います。この時、リストのビュレットはオリジナルの画像にしたいです。

こんな時、背景画像を利用し、以下のような方法でこの見栄えを再現するという方は多いでしょう。筆者も昔からずっと、この方法で実装してきました。


ul {
  list-style-type: none;
}
li {
  padding: 0 0 10px 16px;
  background: url(bullet.png) no-repeat 0 .45em;
}

liの左上を起点に、左から0px、上から0.45emの位置に背景画像としてビュレットを配置するという方法です。

何ら問題のなさそうなこの方法ですが、行の高さや、liの上paddingが変わってしまうと、ビュレットの位置がずれてしまいます。左上を起点に画像を配置しているため、本連載で解説した、アイコン画像をvertical-align:topで配置した場合と似た形になります。例えば、極端にline-heightを高くすれば、以下のようになります。理解しやすさのため、liの上端に赤い線を引いてみました。

こんなに大きくline-heightが変わってしまうことは稀でしょうが、あとからサイト全体のベースとなるfont-sizeline-heightを調節したりした場合、この実装を行った箇所のビュレットは、ほんのわずか上下にずれてしまうかもしれません。そのたびに微調節するのはかなり手間です。

IE6、7でも問題なく利用できるというのは。この実装方法の大きなメリットです。それゆえ、昔から多く使われてきました。

インラインブロックを利用してアイコンを表現する

インラインブロックを利用して、この問題を解決した実装例を紹介しましょう。

前回紹介した実装例では、imgvertical-alignmiddleに配置し、上下にネガティブマージンを指定することで、ビュレットとして利用していました。以下のサンプルでは、それと同じことをbefore擬似要素で実現しています。


ul {
  list-style-type: none;
}
li {
  padding: 0 0 10px 16px;
  text-indent: -16px;
}
li:before {
  content: '';
  display: inline-block;
  background: url(bullet.png) no-repeat 0 0;
  width: 8px;
  height: 8px;
  vertical-align: middle;
  margin: -5px 8px -3px 0;
}

:beforeを利用し、liのはじめにdisplayinline-blockである擬似要素を配置します。

displayinline-blockが指定された要素(これを以降インラインブロックと呼ぶことにします)は、中にブロックレベルの要素を入れることができるコンテナとなります。この場合は中に何も入れないため、それについては特に気にする必要がありません。

このインラインブロック、display:inlineが指定されたインライン要素とは異なり、幅と高さを持ち、行をまたがないという特徴を持ちます。その名の通り、文中に入るブロック要素のような存在です。この場合のレイアウト方法は、文中にimg要素を入れた場合……つまり、インラインの置換要素とほぼ同じです。

この例は、img要素としてビュレットを入れたいが、HTMLをシンプルにしたいゆえ、それを擬似要素で代替しているという具合に考えると分かりやすいかもしれません。ここでポイントとなるのは、この擬似要素がvertical-align:middleで配置されているということです。この状態でline-heightの値を上げ、行間を広げると以下のようになります。わかりやすさのため、middleのラインに赤い線を引いています。

middleのラインにimg要素が揃えられるため、行の高さに影響されず、ほぼ中央配置が維持されます。

このように、img要素の代わりとしてインラインブロックを利用すれば、HTMLをシンプルに保ったまま、vertical-alignの柔軟さを利用することができます。

チェックボックスやラジオボタンの配置

本連載の初めに挙げた疑問として、チェックボックスやラジオボタンをうまく配置するにはどうすればよいのか?という点がありました。

そもそも、チェックボックスやラジオボタンを置いた場合、ブラウザはそれらをどのように描画するのでしょうか。HTMLやCSSの仕様書の中から、そのことについて書かれている箇所を探していたのですが、どうもそれらの実装について明確な取り決めはまだあるわけではなく、ブラウザに内容をよしなに描画するよう任せられているというのが現状のようです。現状の実装においては、たいていOSが持っているコンポーネントがそこに配置されるようになっています。

つまるところ、これらは内容の不明確な置換要素であり、その高さや幅はブラウザによって異なると、とらえるしかなさそうです。そうなると、幅がわからないわけですから、今までtext-indentmarginを利用したビュレットやアイコンの配置方法をそのまま使うだけでは、うまくいきません。

そこで、インラインブロックにしたspan要素でラジオボタンやチェックボックスを囲み、その幅を固定するというアプローチをとったのが、以下の例です。


ul {
  list-style-type: none;
}
li {
  padding: 0 0 10px 25px;
  text-indent: -25px;
}
span {
  display: inline-block;
  width: 20px;
  margin: -17px 5px -13px 0;
  vertical-align: middle;
}
input {
  display: block;
  margin: 0 auto;
}

ここでは先ほどスルーした、インラインブロックの「中にブロックレベルの要素を入れることができる」という特徴を利用しています。あとはこれまでの実装と同様、text-indentで左に飛び出させ、その部分に頭に置きたい要素を置くという方法を利用しています。チェックボックスやラジオボタン(以降、input要素)の幅は不定ですが、それを囲むspan要素の幅が20pxで固定されているため、input要素の幅を気にしなくても良いようになっています。

また、高さについてですが、インラインブロックの高さが特別指定されていない場合、内容により自動的に高さが決定されます。この場合は、input要素の高さがそのまま、span要素の高さとなります。そして、このspan要素は、vertical-align:middleで行中に配置されます。この場合、span要素の中央のラインが行のmiddleラインと揃えられるため、input要素の高さを気にすること無く、行のほぼ中央に揃えられます。

なかなかややこしいですが、インラインブロックの特徴を利用すれば、このように細かなレイアウトも柔軟に表現することができます。

インラインブロックを利用したカラムレイアウト

このように便利なインラインブロックですが、普段floatなりで実現されるようなカラムレイアウトにも利用することができます。まず、この単純な例を見て下さい。


Hello! Hello! Hello!

ただ1行のテキストがあるだけです。
ここに、インラインブロックを一つ突っ込んでみます。


Hello!
...
Hello!

.box {
  display: inline-block;
  vertical-align: middle;
}

インラインブロックは外からはimg要素のようにレイアウトされ、その中にはブロックレベルの要素を入れられるのでした。なので、上記のようにレイアウトされます。

ではここで、テキストを全て取り払ってしまい、インラインブロックだけにしてしまったら……以下のようになります。


...
...
...

これで、インラインブロックを利用したカラムレイアウトの出来上がりです。上図で緑で塗りつぶしている部分は、行を表しています。インラインブロックの中にはブロックレベルの要素を入れることができるため、これが行なのかと言われるとなんとも不思議な感じです。普段は行の中にはテキストが入っていますが、その代わりにインラインブロックだけを詰めてしまえば、それでカラムレイアウトが実現できるというぐらいです。内容が増えれば行が増え、複数段の段組が実現されます。

このように、インラインブロックを利用して、カラムレイアウトを行うこともできます。

floatを利用したカラムレイアウトの問題点

このインラインブロックで行われるようなレイアウトは、旧ブラウザまで対応するとなると、floatを利用して実現することが多いはずです。例えば以下の様な「レイアウトを実現したい」とします。

こんな時、例えば以下の様なコードになりそうです。


...
...
...
...
...

.item {
  float: left;
  width: 25%;
}

一見問題なさそうですが、悲しいことに、この描画結果は以下のようになってしまいます。

細かい話は割愛しますが、このようにfloat:leftされた要素が連続すると、左上から詰めて配置されるようなレイアウトが行われるため、上記のような描画結果になってしまいます。

これは望んだ結果とは異なります。これを解決するには、この場合、例えば4つごとにcler:leftを指定したり、4つごとを別のdiv要素で囲み、clearfix的な指定を行うなどという方法が必要とされます。

インラインブロックを利用した解決方法

こんなとき、インラインブロックを利用すると、シンプルなHTMLで同様のことを実現できます。以下がその例です。


...
...
...
...

.item {
  display: inline-block;
  vertical-align: top;
  width: 25%;
}

これで、望んでいたレイアウトは実現されます。4つごとに何かする必要もありません。先ほどのインラインブロックを利用したカラムレイアウトの例に習えば、ここでは、以下のように、3つの行ボックスがあり、その中にインラインブロックが積まれていると考えられます。

そして、これらインラインブロックは、vertical-align:topで配置されているため、上端で揃えられています。

見た目上はこれが行などとは全く思えませんが、このように行のレイアウトを利用すると、柔軟なカラムレイアウトも実現することができます。現状、これと同じようなことをしたかったら、Flexboxを利用する必要があるように思います。

ちなみに、この例でdiv要素間に入れている変なコメントは、改行やタブを殺すためのものです。改行やタブが存在しているとホワイトスペースがそこにあることになり、そのぶん、領域が確保されてしまいます。このカラムレイアウトの方法自体は、本来行の中で行われる細かな部分のレイアウトを行うための仕組みを利用したものであり、仕様的には正しいものの、トリッキーな応用であるという点を忘れてはいけないでしょう。

まとめ

以上、全4回に渡り、行の中で行われる種々のレイアウト方法について解説してきました。筆者自身、普段何気なく使っていたプロパティでしたが、改めて突っ込んで調べてみたところ、以前よりも意図通りに正確にレイアウトが行えるようになったように感じています。ミクロなレイアウト制御からマクロなレイアウト制御まで、色々と応用の効く知識であると思いますので、CSSの書き方に悩んだらこの連載で取り上げた内容を思い出してみると、応用できる場合もあるかもしれません。

CodeGridについて

この連載の内容は、弊社、株式会社PixelGridが運営するフロントエンドの技術情報を配信するサービス、CodeGridにて連載された内容を元にしています。ご興味があれば、こちらもチェックしていただけると幸いです。

イベント動画

イベントの模様はYoutubeで公開されています。よろしければ、ご覧ください。