HTML
CSS
flexbox
CSSDay 13

Flexbox からコンテンツはみ出る問題を完全に解決する

TL;DR

長い英単語や長いURLに対し

overflow-wrap: break-word;
word-wrap: break-word;

によって(できるだけ禁則処理を回避して)横方向の折り返しを行いたいとき,その対象が Flex アイテムである場合はうまく折り返されずにはみ出てしまう場合がある!その場合,以下の対策を行うべし。

  • flex-direction: row; の場合, Flex アイテムに min-width: 0; を当てる
  • flex-direction: column; の場合, Flex アイテムに max-width: 100%; を当てる
とりあえずこれを使っておけ
* {
    overflow-wrap: break-word;
    word-wrap: break-word;
}
.row {
    display: flex;
    flex-direction: row;
}
.row>* {
    min-width: 0;
}
.col {
    display: flex;
    flex-direction: column;
}
.col>* {
    max-width: 100%;    
}

これだけ見ると「???」って感じだが,↓を見れば納得できるはず!

元ネタ

HTML - word-break: break-word; が効いて word-wrap: break-word; overflow-wrap: break-word; が効かない場合とは?|teratail

Special Thanks: @s8_chu

よくわかる図解

<!DOCTYPE html>

<meta charset="UTF-8">
<title>Flexbox overflow-wrap Test</title>

<style>
    /* 体裁用 */
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: monospace;
    }
    html, body, main {
        height: 100%;
    }
    main {
        width: 640px;
        background: ivory;
        margin: 0 auto;
    }
    article {
        border: 4px solid blue;
        margin: 20px auto;
        background: pink;
    }
    p {
        border: 4px solid red;
        height: 4em;
    }

    /* 横方向 Flex */
    .row {
        display: flex;
        flex-direction: row;
    }
    /* 縦方向 Flex */
    .col {
        display: flex;
        flex-direction: column;
    }
</style>

<main>

    <section>

        <h1>Row</h1>

        <article class="row">
            <p>1</p>
            <p>http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="row">
            <p>2</p>
            <p style="min-width: 0;">http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="row" style="overflow-wrap: break-word;">
            <p>3</p>
            <p style="min-width: 0;">http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

    </section>

    <section>

        <h1>Column</h1>

        <article class="col">
            <p>1</p>
            <p>http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="col" style="overflow-wrap: break-word;">
            <p>2</p>
            <p>http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="col" style="overflow-wrap: break-word; align-items: flex-start;">
            <p>3</p>
            <p>http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="col" style="overflow-wrap: break-word; align-items: flex-start;">
            <p>4</p>
            <p style="max-width: 100%;">http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

        <article class="col" style="align-items: flex-start;">
            <p>5</p>
            <p style="max-width: 100%;">http://example.com/veryveryveryveryveryveryveryveryveryveryveryveryveryverylooooooooooooooooooooooongurl</p>
        </article>

    </section>

</main>

スクリーンショット 2018-12-14 2.52.15.png

スクリーンショット 2018-12-14 3.55.42.png

解説

前提

overflow-wrap: break-word;
word-wrap: break-word;

による単語の折り返しは,あくまで コンテンツがボックスをはみ出る場合 にしか適用されない。 Flexアイテムはコンテンツの大きさに応じてボックスの大きさが変化する 特性を持っており,これがデフォルトだと単語の折り返しよりも優先されてしまうのが失敗の要因。

横方向 Flex

min-width: auto; が Flex アイテムの最小幅の初期値である。この値は同じく初期値である flex-basis: auto; よりも優先して適用されるため,内部のコンテンツの肥大化によって Flex コンテナ領域をはみ出してしまう。そこで

min-width: 0;

を指定すると, flex-basis: auto; が優先して適用されるようになり,アイテムの幅がコンテナ内に収まるように決定される。こうすると「コンテンツがボックスをはみ出している」という状況になるので, 折り返しが効くようになる。

縦方向 Flex

align-items: stretch; が Flex アイテムの横方向アラインメントの初期値である。この場合, Flex アイテムの幅は常に Flex コンテナの幅と一致する。そのため,この値を変更していない場合には折り返しが効かないという問題は発生しない。

ところが align-items: flex-start; などの値を使用した場合, 横方向 Flex の場合と同じように Flex アイテムの幅はコンテンツの大きさによって変化するようになってしまう。そこで

max-width: 100%;

を指定すると,通常のブロック要素と同じようにアイテムの最大幅がコンテナ幅と一致するように決定される。こうすると「コンテンツがボックスをはみ出している」という状況になるので, 折り返しが効くようになる。

なお, Flex コンテナの幅と等しくなるかはみ出ることが確定している一番大きな Flex アイテムに関しては, max-width: 100%; の代わりに width: 100%; または align-self: stretch; を指定しても同じ効果が得られる。

補足

横方向Flex で max-width: 100%; を指定した場合

スクリーンショット 2018-12-14 3.43.15.png

100% は Flex コンテナの幅を意味するため,同じ行にある他の Flex アイテムの幅と同じぶんだけはみ出してしまう。 max-width: calc(100%-残りの要素); を表現したい場合は min-width: 0; が正しいようだ。最大幅の上限を抑える前に,最小幅の下限を広げなければならない,という考え方で説明がつくだろうか。

max-width: none; min-width: auto; であるため 折り返しを考慮しない コンテンツのサイズに従う
max-width: none; min-width: 0; であるため flex-basis: auto; に従う

縦方向Flex で min-width: 0; を指定した場合

何も起こらない。そもそも Flex アイテムの幅に特殊な制約がかかるのは 主軸 方向に限られるため,交差軸方向に関してはブロック要素と同じ考え方でいけるようだ。

max-width: none; min-width: 0; であるため 折り返しを考慮しない コンテンツのサイズに従う
max-width: 100%; min-width: 0; であるため max-width: 100%; に従う

非標準プロパティ word-break: break-word;

teratail の質問中で述べている通り,

word-break: break-word;

はその特異な動きによって特に何も工夫せずともこの問題を解決できるが,非標準プロパティであるためできる限り使わないほうがよい。