話說在前頭:文中的行為我在 Chromium 141 和 153 上實測過,兩個版本結果一樣。其他瀏覽器的支援狀況請以 caniuse 為準。有講錯的地方歡迎指正。

前言

想讓一個元素撐滿容器,第一個想到的通常是 width: 100%。大部分時候它都沒問題,直到元素有了 margin:

.card {
  width: 100%;
  margin: 0 1rem;
}

這張卡片會比容器寬,往右溢出兩倍的 margin。在寬螢幕上可能只是右邊多了一點點,但畫面放大到 400%、寬度只剩 320px 時,就變成整頁出現橫向捲軸。WCAG 1.4.10「重排」要求的,正是放大到這個寬度時不需要橫向捲動。

CSS 與 HTML 技巧清單裡的 stretch 尺寸關鍵字,就是在解決這件事。

先看一下你的瀏覽器支援度:

你的瀏覽器支援度

  • width: stretch偵測中(需要 JavaScript)

一、為什麼 100% 會撐破?

width: 100% 的意思是「寬度等於容器內容區的 100%」,margin 不算在裡面,會再額外加上去。

大家很熟的 box-sizing: border-box,只能把 padding 和邊框算進寬度裡,管不到 margin。我實測一個 400px 的容器,裡面放一個 width: 100%、左右 margin 各 20px 的元素:

寫法元素寬度結果
width: 100%(content-box),加上 5px 邊框、10px padding430px溢出 70px(430 加兩邊 margin 40,比容器多 70)
width: 100% 加 box-sizing: border-box400px還是溢出 40px,就是兩邊的 margin
width: stretch360px剛好塞滿

以前的補救方法是 width: calc(100% - 40px),自己把兩邊的 margin 扣掉。但 margin 一改,calc() 就要跟著改,很容易忘記。

二、width: stretch:撐滿剩下的空間

.card {
  width: stretch;
  margin: 0 1rem;
}

stretch 的意思是「讓 margin 外緣剛好貼齊容器」,也就是容器寬度扣掉 margin 之後剩下的空間。margin 改多少,它都會自動算好。

下面的範例可以切換元素和寫法,再拉動 margin 試試:

互動範例:元素有左右 margin,換不同的 width 寫法,看會不會撐出容器(虛線)

元素
width
這是一個有 margin、邊框和 padding 的區塊

剛好塞滿容器,沒有溢出

偵測中(需要 JavaScript)

此處為互動範例,請在網頁上操作。

可以這樣玩:

  1. <div> 選 100%:溢出的量剛好是兩邊 margin 的總和。
  2. 換成 calc(100% - 32px):margin 剛好是每邊 16px(合計 32px)時會塞滿,但把 margin 拉大或拉小,就又溢出或留空了。
  3. 換成 stretch:不管 margin 拉到多少,都剛好塞滿。
  4. 選 <button> 或 <input>,再選 auto:它們預設不會撐滿,這就是以前要寫 100% 的原因。換成 stretch 一樣剛好塞滿。

三、按鈕和輸入框最常用到

<div> 這類區塊元素,width: auto 本來就會撐滿,而且會扣掉 margin,所以通常不用寫寬度。真正需要 stretch 的,是預設只有內容寬的元素:

  • 按鈕:手機版常見的「整排按鈕」。
  • 輸入框:表單欄位撐滿整欄。

我實測一個 width: 100%、box-sizing: border-box、左右 margin 各 20px 的 <input>,在 400px 的容器裡會溢出 40px;改成 width: stretch 就剛好 360px。

另外 max-width: stretch 也很好用:一個寫死 width: 600px、有 margin 的元素放進 400px 的容器,加上 max-width: stretch 之後被限制在 360px,不會撐破。以前常寫的 max-width: 100% 一樣會被 margin 推出去。

四、在不同排版裡的行為

情況實測結果
容器有 padding撐滿容器的內容區,不會蓋到 padding
height: stretch,容器有固定高度撐滿高度,扣掉上下 margin
height: stretch,容器高度是 auto跟 auto 一樣,由內容決定
絕對定位加 width: stretch; height: stretch撐滿定位的容器
grid 項目,加了 justify-self: start還是撐滿格子
flex 項目(橫排)先當作容器的寬度,再跟其他項目一起被壓縮,不是「剩下的空間」
圖片、表格、inline-block都會撐滿
<span> 這類 inline 元素沒有作用,inline 元素本來就不吃 width

flex 那一列要特別注意:在橫排的 flex 裡想「吃掉剩下的空間」,還是要用 flex: 1,不是 width: stretch。

五、不支援的時候

stretch 是標準名稱,以前各家瀏覽器有自己的私有寫法:Chromium 和 Safari 是 -webkit-fill-available,Firefox 是 -moz-available。

.button {
  width: 100%; /* 最後的備案 */
  width: -webkit-fill-available;
  width: -moz-available;
  width: stretch;
}

瀏覽器會用它看得懂的最後一行。我實測 Chromium 141 兩種寫法都支援,結果一樣是 360px;Firefox 的 -moz-available 我這邊沒辦法測。

六、檢查這個網站

我用 320×256 的視窗(模擬 1280×1024 的螢幕放大 400%),把這個網站的 83 個頁面跑過一次,找出會讓整頁出現橫向捲軸的元素。

  • width: 100% 加 margin 的問題:沒有找到。 網站在 reset 裡已經設定了 box-sizing: border-box,padding 和邊框不會撐破;有 margin 又寫 width: 100% 的元素,實測都沒有超出容器。
  • 找到一個別的問題:英文版 hidden="until-found" 那篇的範例裡,「Collapse all and try again」按鈕寫了 white-space: nowrap,英文字比較長,在 320px 寬時撐出畫面 30px。這跟 stretch 無關,我把 nowrap 拿掉,讓文字可以換行就修好了。

所以這次網站沒有需要改成 stretch 的地方。不過以後寫按鈕、輸入框要撐滿時,我會直接寫 stretch,不再寫 100% 加 calc()。

結論

整理成幾句話:

  • width: 100% 不含 margin,有 margin 時一定會撐破容器;box-sizing: border-box 只管得到 padding 和邊框。
  • width: stretch 撐滿容器扣掉 margin 後的空間,margin 怎麼改都會自動算好。
  • 最常用在按鈕、輸入框這類預設不撐滿的元素;max-width: stretch 也能防止固定寬度的元素撐破。
  • 橫排 flex 裡要吃剩下的空間,還是用 flex: 1。
  • 不支援時,前面加上 -webkit-fill-available、-moz-available 當備案。

撐破容器的那幾十 px,在放大畫面的使用者眼中就是一條橫向捲軸。少寫一個 calc(),也少一個放大時出錯的機會。

延伸閱讀

相關連結