検索

記事と日記を検索…

手機版卡片被撐破:一個 min-width: auto 引起的水平溢出

2026-08-30 に記す・處暑

この記事はまだ日本語に翻訳されていません。原文を表示しています。

他の言語: 繁體中文

手機上打開自己的專案,發現卡片右半邊被切掉了一塊,程式碼編輯器整個溢出到畫面外。第一直覺是「編輯器沒設好 overflow」,於是往 CodeMirror 上加了各種 class——結果一個都沒用。

真正的兇手在完全不同的地方,而且它是 CSS 裡最經典的陷阱之一:min-width: auto。

TL;DR

flex 和 grid 的子元素,預設 min-width 是 auto 而不是 0。這代表它不會縮到比內容更小。所以只要裡面有一段不換行的長內容,它就會把外層一路往外推,就算你已經寫了 w-full 也一樣。

修法是找出真正被撐大的那一層(往往不是視覺上爆開的那個元件),在它身上加 min-width: 0(Tailwind 的 min-w-0)。

一、症狀

專案首頁有一張卡片,裡面是 CodeMirror 程式碼編輯器。在桌機上一切正常,但用手機開啟、貼進一段比較長的程式碼(例如 Mermaid 的 sequenceDiagram,有些行很長)之後:

  • 卡片右側被切掉,超出畫面
  • 整個頁面可以左右滑動
  • 編輯器的長行沒有在自己的框內捲動,而是直接把卡片撐破

手機上卡片右側被切掉,程式碼編輯器溢出畫面手機上卡片右側被切掉,程式碼編輯器溢出畫面

用 375px(iPhone SE)的視窗實際量:

高いほど良い
視窗寬度 viewport375 px×1.2
頁面實際寬度 scrollWidth最良432 px

溢出了 57px。

二、一連串沒有用的修法

因為畫面上「看起來」是編輯器爆出來,我很自然地往它身上加東西:

全 4 項目

這裡犯的錯

一直在「看起來壞掉的那個元件」身上找答案。但 CSS 的溢出問題,視覺上爆開的地方,往往不是實際失控的那一層。真正該做的第一件事是把每一層的寬度量出來,而不是猜。

三、關鍵:從內往外量一遍

與其猜,不如直接把從最內層到 <body> 的每一層寬度全部印出來。用 Playwright 跑一次:

JavaScript
const chain = await page.evaluate(() => {
  const out = [];
  let el = document.querySelector(".cm-content"); // 從最內層開始
  while (el && el !== document.body) {
    const cs = getComputedStyle(el);
    const r = el.getBoundingClientRect();
    out.push({
      cls: el.className.toString().slice(0, 40),
      w: Math.round(r.width),
      scrollW: el.scrollWidth,
      minW: cs.minWidth,      // ← 重點看這個
      overflowX: cs.overflowX,
    });
    el = el.parentElement;    // 一路往外走
  }
  return out.reverse();       // 反轉成由外到內
});

結果一目了然(由外到內):

テキスト
w= 375  min=0px    ← body 下的容器,正常
w= 375  min=0px    ← 根層的 grid,正常
w= 432  min=auto   ← ⚠️ 就是這一層!被推到 432px
w= 384  min=auto   ← 卡片外層(w-full max-w-sm)
w= 384  min=0px    ← Card
w= 352  min=0px    ← Tabs
w= 352  min=0px    ← 表單容器
w= 350  min=0px    ← .cm-editor
w= 350  sw=508     ← .cm-scroller(有正常捲動)
w= 473  min=auto   ← .cm-content(長行的真實寬度)

編輯器內部其實是正常的——.cm-scroller 自己寬 350px、內容 508px,水平捲動有在運作(這是前面第 ③ 步的功勞)。

真正失控的是那個 w=432 的頁面外層:它的父層只有 375px,它卻長到 432px。從它以下每一層都被連帶撐開,卡片因此變成 384px,超出畫面。

四、根因:min-width: auto

那一層的 class 是這樣的:

HTML
<div class="flex min-h-svh w-full items-center justify-center p-6">

它是外層 grid 的子元素(grid item)。這裡有一條很多人不知道的規則:

flex item 和 grid item 的 min-width / min-height,初始值是 auto,不是 0。
在 auto 之下,元素不會被壓縮到小於它內容的最小尺寸。

也就是說,就算你寫了 w-full(width: 100%),只要裡面有一段不肯換行的內容,min-width: auto 就會推翻這個寬度,把元素撐到內容需要的大小。

算一下數字剛好對上:

テキスト
卡片 max-w-sm    = 384px
外層 p-6 左右內距 = 24 × 2 = 48px
                   ─────────────
總計               432px  ← 完全吻合量到的數字

整條因果鏈長這樣:

擋不住,因為問題在更外面 ✅ 真正的解法 CodeMirror 裡有一行很長的程式碼(.cm-content 寬 473px) 外層是 flex/grid itemmin-width: auto 不肯縮到比內容小被撐到 432px 卡片跟著變寬 384px(空白時本來只有 327px) 超出 375px 視窗畫面右邊被切掉 在更裡層加 overflow-hidden 在 432px 那層加 min-width: 0

那 overflow: hidden 到底行不行?

我實測過:在同一層(那個 432px 的元素)改用 overflow-x: hidden,畫面確實也不溢出了,數字跟 min-w-0 一模一樣。所以它不是「無效」。

差別在於做的事情不同:

  • min-width: 0 → 讓元素縮回它該有的大小(432px → 375px)
  • overflow-x: hidden → 元素還是 432px,只是超出的部分被裁掉不給看

前者解決成因,後者遮蔽結果。之所以選前者,是因為被裁掉的內容依然佔著空間、只是看不見,之後很容易衍生「明明有東西卻點不到 / 捲不到」的問題。而且裁切點放錯一層,就會連該顯示的內容一起吃掉——我在第 ④ 步就是這樣,把該捲動的區域也裁了。

五、修法

在被撐開的那一層加上 min-width: 0:

TSX
function HomeComponent() {
  return (
    <div className="flex min-h-svh w-full items-center justify-center p-6">
      <UploadPanel />
    </div>
  );
}

一個 class,就這樣。 修完再量一次:

低いほど良い
修改前 頁面寬度432 px×1.2
修改後 頁面寬度最良375 px

375px 視窗、375px 頁面寬度,完全吻合,沒有任何水平溢出。卡片也回到正常的 327px:

修好後卡片完整置中,長行改在編輯器內部水平捲動修好後卡片完整置中,長行改在編輯器內部水平捲動

長行沒有消失,它改成在編輯器自己的框裡水平捲動,這才是原本就該有的行為。

六、怎麼判斷該在哪一層加 min-w-0

不用猜,把上面那段量寬度的程式碼貼進瀏覽器 Console 就行(把 .cm-content 換成你那個爆出來的元素):

JavaScript
let el = document.querySelector("你的元素選擇器");
while (el && el !== document.body) {
  const cs = getComputedStyle(el);
  const w = Math.round(el.getBoundingClientRect().width);
  console.log(w, cs.minWidth, el.className.toString().slice(0, 50));
  el = el.parentElement;
}
console.log("viewport:", innerWidth, "頁面:", document.documentElement.scrollWidth);

判讀方式很簡單:

找出兇手的規則

由外往內看,找出第一個「寬度大於它父層」的元素。如果它的 min-width 是 auto,而且它是 flex 或 grid 的子元素——就是它。在它身上加 min-w-0。

七、教訓

幾個帶得走的重點

  • flex / grid 子元素的 min-width 預設是 auto,不是 0。這是絕大多數「莫名其妙水平溢出」的根源。同理還有 min-height: auto 造成的垂直溢出。
  • w-full 不保證元素不會變寬。min-width: auto 的優先度比 width 高,內容撐得動它。
  • 視覺上爆開的元件,通常不是失控的那一層。 與其在它身上一直加 class,不如從內往外把每一層的寬度和 min-width 印出來,30 秒就能定位。
  • overflow: hidden 能蓋掉症狀,但蓋的不是同一件事。 它讓元素維持過大的尺寸、只是裁掉看不到的部分;min-width: 0 才是讓元素縮回正確大小。能修成因就別只遮結果。
  • 需要讓內層自己捲動(例如程式碼編輯器、表格)時,記得整條鏈上的每一層都要能縮——中間任何一層漏掉 min-w-0,捲動就不會生效。

丙午年 處暑

唯一 書

コメント欄を読み込み中...