CSSのfloatプロパティは、画像などにテキストを回り込ませたい時に使える。代替技術もあるが、floatはその手軽さが魅力だ。

floatのはたらき

CSSでは、行と段落に基づいたレイアウトを基本としている。1行に収めるインライン要素と、前後で改行するブロック要素の組み合わせである。テキストの流れに沿って整列されることから、フローレイアウトと呼ばれる。

floatプロパティを指定すると、その要素はこのフローレイアウトのルールから外れて、コンテナ要素内の左右どちらかに寄る。コンテナがインラインであってもブロックであっても、floatプロパティは作用する。

もしコンテナ内に別の要素(あるいはテキスト)がある場合、それはフロート要素を回り込むように表示される。

基本的にはコンテナの上端に合わせて表示されるが、幅が足りなくなると自分を下げていく。また、コンテナに高さが指定されている場合、高さが足りなくなるとはみ出る(position:absolute;を指定した場合と同じく、そもそもコンテナの高さの計算には含まれていない)。

基本的な使い方

floatプロパティには、以下のような値が用意されている。noneを除くこれらのいずれかを要素に指定すると、その要素はフロート要素となる。とてもシンポー(シンプル)。

floatプロパティの値 概要
left 要素を左に寄せる。
right 要素を右に寄せる。
inline-start 要素を書字方向の先頭側に寄せる。
inline-end 要素を書字方向の末尾側に寄せる。
none フロートを解除する。

inline-startinline-endが一見ややこしそうだが、これは単純に文字の方向を基準にしているだけで、左右のどちらに寄せるというfloatの働きは変わらない。

デフォルトでは、テキストは左から右に流れる。そのため、leftinline-startrightinline-endは同じ意味を持つ。

右から左に流れるテキストであれば、inline-startrightinline-endleftと同じ意味を持つ。

html
<html>
  <head>
    <meta charset="UTF-8" />
    <style>
      #bg{
        display:block;
        margin:1em 0;
        background-color:white;
        height:7em;
      }
      #container{
        background-color:gray;
      }
      #float{
        background-color:gold;
        border:dashed 1px black;
        color:black;
        width:6em;
        height:6em;
        float:left;
        opacity:0.8;
      }
      #sibling{
        background-color:lightgray;
        border:dashed 1px black;
        color:black;
        display:inline;
        width:15em;
        height:6em;
      }
    </style>
  </head>
  <body>
    <script
      type="text/javascript"
      src="https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.0.nomodule.min.js"
    ></script>
    <script>
      const { div, p, span,fieldset, legend, input, label } = van.tags;

      const SampleBox = () => {
        return div({id:'bg'},
          p( {id: 'container'},'私は#containerのコンテンツ。ただのテキスト。',
            span({ id: 'float' },'私は#float。浮浪徒って書くとちょっと詩的。'),
            span({ id: 'sibling'},'私は#floatの兄弟。あるいは兄妹。はたまた姉妹。もしくは姉弟。浮浪する妹、あるいは弟。それがフロート。')
        )
        );
      };

      const PropsToggler = ({
        targetId,
        targetPropName,
        groupName,
        defaultValue,
        values,
      }) => {
        const onChange = (e) => {
          const target = document.querySelector(`#${targetId}`);
          if (!target) {
            console.error(`The element with ID:${targetId} does not exist.`);
            alert(`Error: The element with ID:${targetId} does not exist.`);
            return;
          }
          target.style[targetPropName] = e.target.value;
        };

        return div(
          OptionGroup({
            groupName: groupName,
            defaultValue: defaultValue,
            values: values,
            onchange: onChange,
          })
        );
      };

      const OptionGroup = ({
        groupName = '',
        values = [],
        defaultValue = '',
        onchange = () => {},
      }) => {
        return fieldset(
          legend(groupName),
          ...values.map((v) =>
            label(
              input({
                type: 'radio',
                name: groupName,
                value: v,
                onchange: onchange,
                checked: defaultValue === v,
              }),
              v
            )
          )
        );
      };

      van.add(
        document.body,
        PropsToggler({
          targetId: 'container',
          targetPropName: 'display',
          groupName: '#containerのdisplayプロパティ',
          defaultValue: 'block',
          values: ['block', 'inline'],
        }),
        PropsToggler({
          targetId: 'container',
          targetPropName: 'direction',
          groupName: '#containerのdirectionプロパティ',
          defaultValue: 'ltr',
          values: ['ltr', 'rtl'],
        }),
        PropsToggler({
          targetId: 'float',
          targetPropName: 'float',
          groupName: '#floatのfloatプロパティ',
          defaultValue: 'left',
          values: ['none', 'left', 'right' ,'inline-start', 'inline-end'],
        }),
        PropsToggler({
          targetId: 'float',
          targetPropName: 'display',
          groupName: '#floatのdisplayプロパティ',
          defaultValue: 'block',
          values: ['block', 'inline'],
        }),
        SampleBox,
        PropsToggler({
          targetId: 'sibling',
          targetPropName: 'float',
          groupName: '#siblingのfloatプロパティ',
          defaultValue: 'none',
          values: ['none', 'left', 'right' ,'inline-start', 'inline-end'],
        }),
        PropsToggler({
          targetId: 'sibling',
          targetPropName: 'display',
          groupName: '#siblingのdisplayプロパティ',
          defaultValue: 'inline',
          values: ['block', 'inline'],
        }),
      );
    </script>
  </body>
</html>

テキストの方向は、CSSのdirectionプロパティで指定できる。ただ、MDNによると、可能な限りCSSではなくHTMLのdir属性を用いるべきとのことだ。

理由は明記されていない(あるいは見つけられなんだ)が、たぶんアクセシビリティ的なことだろう。CSSではテキストの方向を支援デバイスに伝えられないから、なるべくHTMLを使って明示しろ、という意味だと思う。

また、writing-mode: vertical-rl;などを指定すると、左右ではなく上下にフロートさせることもできる(あまり使いどころはないかもしれないが)。

floatのおもしろい特徴

floatプロパティには、いくつかのおもしろい(そして注意が必要な)特徴がある。

displayプロパティをblockにする

floatプロパティを指定した要素は、displayプロパティの値が内部的にblockに変更される。

例えば<span>などのインライン要素にfloatを指定するとブロック要素になる。inline-flexinline-gridを指定している要素は、flexgridとして計算し直される。

html
<style>
  .container {
    background-color: gray;
    box-sizing: border-box;
    padding: 1em;
    width: 100%;
  }
  span {
    background-color: black;
    color:white;
  }
  .display-block {
    display: block;
  }
  .float-right {
    background-color: gold;
    border: dashed 1px black;
    color: black;
    float: right;
  }
</style>
<div class="container">
  <span class="float-right">私は`float: right;`が指定されたspanです。</span>
  <span>私はspanです</span>
  <span class="display-block">私は`display: block;`が指定されたspanです。</span>
</div>

要素の順番で回り込み方が変わる

floatプロパティを指定した要素は、コンテナ要素の上端に寄せられる。

十分な横幅がない場合、フロート要素より先に幅を占める要素(例えばブロック要素)があると、その要素の下に落ちる。

一方、フロート要素より後に幅を占める要素がある時は、フロート要素が上に来る。

html
<style>
  .container {
    background-color: gray;
    box-sizing: border-box;
    padding: 1em;
    width: 100%;
  }
  span {
    background-color: black;
    color:white;
  }
  .display-block {
    display: block;
  }
  .float-right {
    background-color: gold;
    border: dashed 1px black;
    color: black;
    float: right;
  }
</style>
<div class="container">
  <span>私はspanです</span>
  <span class="display-block">私は`display: block;`が指定されたspanです。</span>
  <span class="float-right">私は`float: right;`が指定されたspanです。下に移動しました。</span>
</div>

コンテナからサイズを無視される

floatプロパティを指定すると、その要素はposition: absolute;を指定した時のように、そのコンテナのフローレイアウトから外れる。要素自体に高さがあっても、それがコンテナの高さに影響を与えることがなくなる。

親要素のdisplay: flow-root;を指定すると、フロート要素の高さが計算されるようになる。

html
<style>
  .container {
    background-color: gray;
    text-align: center;
    width: 100%;
  }
  .container:nth-of-type(2){
    margin-top:2em;
  }
  .flow-root {
    display: flow-root;
  }
  p {
    background-color: black;
    color: white;
    margin: 0;
    padding: 0.5em;
  }
  .float {
    display: block;
    background-color: gold;
    border: dashed 1px black;
    box-sizing: border-box;
    padding: 2.25em 0;
    line-height: 1.5em;
    width: 6em;
    opacity: 0.8;
    overflow: hidden;
  }
  .float--left {
    float: left;
  }
  .float--right {
    float: right;
  }
</style>
<div class="container">
  <div class="float float--left">float-left</div>
  <div class="float float--right">float-right</div>
  <p>floatを指定された要素は、親要素の高さにはカウントされない。この点は`position: absolute;`と似ている。</p>
</div>
<br>
<div class="container flow-root">
  <div class="float float--left">float-left</div>
  <div class="float float--right">float-right</div>
  <p>親要素に`display:flow-root;`を指定すると、しっかり高さが含まれる(灰色の部分がコンテナの背景)。</p>
</div>

回り込むのはテキストだけで、ブロックは重なる

コンテナ内の要素は、floatプロパティを指定した要素を回り込む。このとき、回り込む要素がブロック要素かインライン要素かによって、挙動に違いがある。

ブロック要素の場合、ボックス自体は回り込まず、中のコンテンツだけが回り込む(ブロック要素のボックスは、フロート要素を無視して、デフォルトのままに振る舞う)。一方、インライン要素の場合は、ボックスごと回り込む。

html
<style>
  .container {
    background-color: gray;
    text-align: center;
    width: 100%;
    height: 120px;
  }
  .container:nth-of-type(2){
    margin-top:2em;
  }
  p,
  span {
    background-color: black;
    color: white;
    margin: 0;
    padding: 0.5em;
  }
  .float {
    display: block;
    background-color: gold;
    border: dashed 1px black;
    box-sizing: border-box;
    padding: 2.25em 0;
    line-height: 1.5em;
    width: 6em;
    opacity: 0.8;
    overflow: hidden;
  }
  .float--left {
    float: left;
  }
  .float--right {
    float: right;
  }
</style>
<div class="container">
  <div class="float float--left">float-left</div>
  <div class="float float--right">float-right</div>
  <p>これはブロック要素です。サイズを指定しない限り、要素のボックスはコンテナ幅に広がります。ただしテキストなどのコンテンツはフロート要素を回り込みます。</p>
</div>
<div class="container">
  <div class="float float--left">float-left</div>
  <div class="float float--right">float-right</div>
  <span>これはインライン要素です。インライン要素に縦方向のpaddingを指定しても、要素の高さに影響を与えません。だからコンテナ要素をちょっぴり貫通していますが、仕様通りの挙動であり、決して間違いではなく、なんら恥じるところはありません。</span>
</div>

回り込みは子要素のclearプロパティで解除する

フロート要素の回り込みを途中で解除したい時は、回り込む要素にclearプロパティを指定する。

このプロパティに指定できる値は以下の通り。

floatプロパティの値 概要
left float: left;が指定された要素への回り込みを解除。
right float: right;が指定された要素への回り込みを解除。
both 両側への回り込みを解除。
inline-start float: inline-start;が指定された要素への回り込みを解除。
inline-end float: inline-end;が指定された要素への回り込みを解除。
none 何もしない。

なお、以下のケースではclearプロパティが期待通りに機能しないため注意が必要である。

  • 親要素にfloatプロパティが指定されている。
  • インライン要素にclearプロパティを指定している。

また、 片側だけ回り込みを解除したい場合に、解除するフロート要素よりも反対側のフロート要素の高さが低い場合、見かけ上clear: both;を指定した場合と同じ結果になることがある。

floatプロパティが効かない時に考えられること

回り込む要素がブロック要素で、かつ幅が回り込むフロート要素より小さいと、回り込む余地なくなる。そのため、floatプロパティは効いているのに、見かけ上は効いていないように見えることがある。

html
<style>
.container {
  background-color: gray;
  text-align: center;
  width: 100%;
}
p {
  background-color: black;
  color: white;
  margin: 0;
  padding: 0.5em;
  width: 50%;
}
.float {
  display: block;
  background-color: gold;
  border: dashed 1px black;
  box-sizing: border-box;
  padding: 2.25em 0;
  line-height: 1.5em;
  width: 50%;
  opacity: 0.8;
  overflow: hidden;
}
.float--left {
  float: left;
}
</style>
<div class="container">
  <div class="float float--left">float-left</div>
  <p>ブロック要素のボックスは、フロート要素を無視して配置される。しかしその中身は、フロート要素を回り込む。ボックスの幅がフロート要素より狭いと、回り込む余地がなくなり、下に下ろされしまう。</p>
</div>

この場合、ボックスの幅をフロート要素より大きくすれば回り込みが発生する。

参考