dorapon2000’s diary

忘備録的な。セキュリティとかネットワークすきです。

左右を固定幅の要素に挟まれた可変幅の要素の長い文字列を省略したい

この記事は巷であふれている「flex: 1; + text-overflow: ellipsis; の組み合わせが罠だから min-width: 0; をつけよ」という記事になります。 それを知った今検索すると、嫌でも目に付く内容なのですが、実装しているときはまったく気づくことができず沼っていました。 作りたい対象をタイトルにして、自分も記録に残します。

この記事中のソースコードGitHub にあげています。

github.com

実現したい UI

最終的なコード

<!-- index.html -->
<div class="element">
  <div class="left-container">
    <div class="icon"></div>
    <div class="title">とても長いタイトルがここに表示されますとても長いタイトルがここに表示されますとても長いタイトルがここに表示されますとても長いタイトルがここに表示されます</div>
  </div>
  <div class="right-container">
    <button>詳細</button>
    <button>編集</button>
  </div>
</div>
// style.scss
.element {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background-color: #fff;
  border: 1px solid #ddd;
  border-radius: 6px;

  .left-container {
    display: flex;
    flex: 1;
    gap: 8px;
    align-items: center;
    min-width: 0; // <-- 一番伝えたい注意ポイント

    .icon {
      width: 32px;
      height: 32px;
      background-color: #d0e0ff;
    }

    .title {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
  }

  .right-container {
    display: flex;
    gap: 8px;
  }
}

見やすさの関係で css は scss で記載しています。css が必要な方はリポジトリにあります。

解説

長い文字列を省略する

まずは簡単なところから解説します。

      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;

よくある組み合わせです。nowrap で改行を禁止して、hidden であふれた文字列を隠して、ellipsis で隠れた分を「...」表記させます。

可変と固定

    flex: 1;

Flexbox で唯一その要素の長さだけを可変にする場合、flex: 1; これによって、他の要素に影響を与えずに、画面幅に応じて、その領域は余ったスペースを埋める形で伸びてくれます。たぶん正確な説明ではないですが、おおよそこう認識しています。

今回は2か所で flex: 1; が使われています。まず、left-container に flex: 1; があります。right-container が固定幅なので、left-container は残った領域を可変で埋めてほしいです。title にも flex: 1; があります。これも icon に影響を与えずに、残った領域を埋めてほしいからです。

「他の要素に影響を与えずに」の考察

少し寄り道して、flex: 1; について深く理解していきます。キーワードは flex-basis: 0%; です。

もし、title の flex: 1; がない場合、アイコンの領域が width よりも狭くなってしまいます。

しかし、タイトルが短い場合は問題ありません。

この現象を自分もうまく言語化できないのですが、とりあえず、 flex 指定しない場合のデフォルト値が

flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;

であり、flex: 1

flex-grow: 1;
flex-shrink: 1;
flex-basis: 0%;

のショートハンドであることは知っておく必要があります。また、width よりも flex-basis が優先されflex-grow: 1; だけを title に指定してもバグは直りません。

ここからは私の推測です。

タイトルが長すぎるとどの要素を縮めるかをブラウザが判断します。「縮める」なので flex-grow は今回関係ありません。flex: 1; を指定しないとき、icon と title はどちらもデフォルト値の flex-shrink: 1;flex-basis: auto; です。 一方、icon にだけ width 指定がありますが、これは flex-basis が優先されるため無視されるみたいです。両要素の flex-shrink flex-basis が対等なとき、ブラウザが平等に両方をいい感じに縮めようとします。その結果が icon が狭くなる現象につながっているようです。

title に flex: 1; を付けた場合。flex-shrink: 1 は変わりませんが、title だけ flex-basis: 0% になります。ブラウザは icon と title を対等だとみなさず、flex-basis が大きいほうが width まで大きさを保ってよいと判断するようです。その結果、「他の要素に影響を与えずに」title だけ領域いっぱいを占有するということです。

タイトルが別に長くなければ、どの要素を縮めるかという話にすらなず、icon は width の領域を確保します。

ここまで私の推測です(強調)。flex 周り難しい...

min-width: 0;

flex: 1; には min-width: 0; を付けないと、overflow: hidden; 指定しているにも関わらず、領域内にテキストが収まってくれなくなります。 これがこの記事の一番伝えたいことでした。

調べてみると、flex: 1;min-width: 0; はよくあるハマりポイントで、暗黙のうちにセットみたいな感じがします。ただ直る理由はよくわかりませんでした。なので前振りが長かった割に解説はできません... カナシイ

ないよりはましかと思って、ChatGPT くんによる簡単な解説を載せておきます。

Flexアイテムのデフォルトのmin-widthはautoなので、コンテンツ幅を守ろうとしてしまう。 それを0にすることで「どんなに小さくてもいいから収まって」と命令できる。

まとめ

  • 可変にしたい要素には flex: 1; を指定する
  • その上で、改行させずに text-overflow: ellipsis; する場合は min-width: 0; を付ける
  • width よりも flex-basis が優先されるが、その解釈はブラウザの気分

参考サイト