flexboxで文字が1文字ずつ改行される|wrapを疑って、半日直せなかった原因
スマホの絞り込みタブを見たら、文字が一文字ずつ縦に積み上がっていた。
flex-wrap: wrapを外しても、幅を変えても直らない。
原因は「縮みすぎ」だった。
スマホでサイトを開いて、思わず二度見した。
絞り込み用に並べていたタブが、横並びのはずなのに、文字が一文字ずつ縦に積み上がっている。
「電源あり」というただの4文字が、電、源、あ、り、と改行されて縦一列になっていた。
PCの画面では何も問題なかった。
崩れていたのは、スマホの実機で見たときだけだった。
作っていたのは、ただの絞り込みタブだった
キャンプ場の一覧ページに、設備で絞り込むためのタブを追加していた。
「電源あり」「わんこ可」「直火OK」といったチップを横一列に並べて、押すと一覧が絞り込まれる、よくある作りだ。
CSSはflexで横並びにして、画面が狭いときは折り返すようにflex-wrap: wrapを付けておいた。
自分のブラウザで確認したときは、ちゃんとチップの形のまま2行に折り返していたので、これで終わりだと思っていた。
数日後、実機のスマホで開いて気づいた。
チップの見た目そのものが崩れていた。
横幅がタブの数だけ潰れていって、最後には文字そのものが縦書きのように積み上がっていた。
wrapを疑ったのに、直らなかった
最初はflex-wrapの効きが悪いのだと思った。
wrapをnowrapにしてみたり、flex-basisを固定してみたり、順番に試した。
どれも症状は変わらない。
チップの横幅を強制的に広げても、次のリロードでまた同じように潰れることがあった。
そこでやっと、崩れているのが「折り返し」ではなく「縮み」だと気づいた。
行として折り返せていないのではなく、ひとつひとつのチップの横幅そのものが、文字が入らないところまで縮んでいた。
flexアイテムは、放っておくと文字の幅より縮む
調べて分かったのは、flexアイテムのflex-shrinkの初期値は1で、コンテナが狭くなると子要素は自分から縮もうとするという仕様だった。
普通の英語のテキストなら、これくらいでは崩れない。
単語はスペースの位置でしか改行できないので、縮んでも単語ごと次の行に落ちるだけで済む。
ところが日本語の文章には、その「改行していい場所」の目印がない。
ブラウザは日本語の文字ならどこで区切っても構わないと判断するので、横幅が足りなくなった瞬間、文字と文字の間で平気で改行してしまう。
チップの横幅が縮み続けて文字1文字分より狭くなると、残る逃げ場は縦方向しかなくなる。
「電源あり」が縦に4段積みになっていたのは、そういう理屈だった。
flex-wrap: wrapは「行」の折り返しを担当しているだけで、チップの中の文字がどこで改行されるかは、また別の設定が決めている。
僕はそこを混同していたのだと思う。
直したのは、たった2行だった
チップ側にwhite-space: nowrapとflex-shrink: 0を足した。
white-space: nowrapで、チップの中の文字が途中で改行されるのを禁止する。
flex-shrink: 0で、チップそのものが横幅より縮むのを禁止する。
この2つを足しただけで、タブは文字の形を保ったまま、必要な分だけ折り返すようになった。
直してから、他のページのタブやピル型のボタンも同じ構造になっていないか見直した。
似たような一覧が3か所あって、そのうち2か所で同じ崩れ方を実機で確認した。
PCのブラウザの画面幅を縮めるだけでは、ここまで極端には縮まなかった。
実際のスマホの実幅、375から390pxあたりで見て、初めて気づける崩れ方だったのではないだろうか。
いまはコーディングのルールに書き足してある
flexで横に並べた文字要素には、white-space: nowrapとflex-shrink: 0を必ずセットで付ける。
flex-wrap: wrapだけでは足りない、という一文を、自分用の規約に足した。
崩れる条件がスマホの実機幅に依存していて、デスクトップの検証だけでは再現しづらいことが厄介だった。
次に同じ形のUIを作るときは、この2行を先に書いてから中身を詰めるようにしている。
ほかにも書いています
note・スタンプ※noteは運営者が個人で書いているものです。
