Learning Objectives
- 說明一般流(normal flow)的排列方式,以及 position: static 的意思
- 分辨 relative、absolute、fixed、sticky:是否留在一般流、相對於哪一個包含區塊
- 用 top、right、bottom、left 指定位移,並找出 absolute 元素的包含區塊(最近的非 static 祖先)
- 用 z-index 調整重疊的順序,說出 z-index 只對有定位的元素有效,並知道堆疊環境的存在
- 做出卡片角落的標籤、黏在上方的頁首、固定在畫面角落的按鈕
讓元素離開一般的排列
到目前為止,元素都是一個接一個排好:區塊從上到下,行內元素從左到右。這種排列方式叫做一般流(normal flow), 元素彼此不會重疊,前面的元素變高,後面的元素就往下移。
但有些設計需要「疊上去」:
| 設計 | 需要的效果 |
|---|---|
| 卡片右上角的「NEW」標籤 | 貼在卡片角落,不佔卡片內文的空間 |
| 捲動時一直留在上方的頁首 | 捲到它的時候黏住,不跟著捲走 |
| 畫面右下角的「回到頂端」或「報名」按鈕 | 不管捲到哪裡都在同一個位置 |
| 下拉選單、提示框 | 蓋在其他內容上面 |
這些都由 position 屬性處理。它是 CSS 裡最容易「一改就整頁亂掉」的屬性,關鍵在於弄清楚每個值相對於誰定位。
position 的五個值
| 值 | 還在一般流裡嗎(保留空間) | top、left… 相對於 | 常見用途 |
|---|---|---|---|
static(預設) |
是 | 沒有作用 | 一般內容 |
relative |
是,原本的空間保留 | 自己原本的位置 | 微調位置;當 absolute 子元素的參考點 |
absolute |
否,不佔空間 | 最近一個 position 不是 static 的祖先 | 角落標籤、下拉選單 |
fixed |
否 | 視窗(viewport),捲動也不動 | 固定頁首、浮動按鈕 |
sticky |
是 | 平常像 relative;捲到門檻時黏在捲動容器的邊緣 | 黏在上方的頁首、表格標題列 |
搭配的四個屬性 top、right、bottom、left 表示從參考邊緣往內的距離:
.badge {
position: absolute;
top: 8px; /* 離包含區塊的上緣 8px */
right: 8px; /* 離包含區塊的右緣 8px */
}
同一個方向通常只寫一個(top 或 bottom、left 或 right)。position: static 的元素寫這四個屬性完全沒有作用,這是最常見的「怎麼改都不動」的原因。
relative、absolute 與包含區塊
relative:元素先照一般流排好,畫的時候再位移。原本的空間還在,旁邊的元素不會補上來,所以位移後可能和別人重疊。
.note { position: relative; top: 10px; left: 20px; } /* 往下 10px、往右 20px */
absolute:元素離開一般流,不佔空間,後面的元素會補上它原本的位置。它的位置相對於包含區塊(containing block): 從父元素開始往上找,第一個 position 不是 static 的祖先,以它的 padding 邊緣為準。 如果一路找到最外層都是 static,就以初始包含區塊為準:大小和視窗一樣、從網頁最上方開始的區域。
所以最常見的寫法是父層設 relative(不寫任何位移,只是當參考點),子層設 absolute:
.card { position: relative; } /* 當參考點,自己不會移動 */
.badge { position: absolute; top: 8px; right: 8px; } /* 貼在 .card 的右上角 */
忘了給 .card 設 relative,標籤就會一路往上找,最後跑到整個網頁的右上角。
小測驗:relative 與 absolute
一個段落設了 position: relative; top: 20px;,它後面的段落會怎樣?
.badge { position: absolute; top: 0; right: 0; }。它的父元素是 static,祖父元素是 position: relative,再上一層也是 relative。標籤會貼在誰的右上角?
把一個段落改成 position: absolute;,但沒有寫 top、left。會發生什麼事?
fixed 與 sticky
fixed 相對於視窗定位,網頁捲動時它不動:
.back-to-top { position: fixed; right: 16px; bottom: 16px; } /* 永遠在畫面右下角 */
.site-header { position: fixed; top: 0; left: 0; right: 0; } /* 固定在最上方、左右撐滿 */
body { padding-top: 56px; } /* fixed 不佔空間:要留出頁首的高度,否則第一段內容會被頁首蓋住 */
sticky 平常留在一般流裡,捲動到你設定的門檻時黏住:
.site-header { position: sticky; top: 0; } /* 捲到頂端時黏在上緣 */
- 一定要寫門檻(
top、bottom等至少一個),沒寫的話它就和 relative 一樣,不會黏住。 - 它只在父元素的範圍內黏住:父元素捲出畫面時,它也跟著被帶走。所以 sticky 的頁首通常放在 body 底下,不要包在一個矮的容器裡。
- 它黏在最近一個會捲動的祖先(
overflow是 hidden、scroll、auto 的元素)上;某個祖先設了overflow: hidden,常會讓 sticky 看起來「失效」。
fixed 的頁首會一直佔著畫面高度;手機畫面不大,固定的頁首與按鈕要盡量矮、盡量少。
z-index:誰疊在上面
元素重疊時,預設的順序是:有定位的元素蓋在沒定位的上面;同樣有定位的,HTML 裡寫在後面的蓋在上面。
要改變順序,用 z-index,數字大的在上面:
.site-header { position: sticky; top: 0; z-index: 10; } /* 捲動時蓋在內容上面 */
.dropdown { position: absolute; z-index: 20; }
z-index只對有定位的元素(position 不是 static)有效;Flex 與 Grid 的項目也可以用。對一般的 static 元素寫 z-index 沒有作用。- 可以是負數;
auto是預設值。
堆疊環境(stacking context):一個元素設了 z-index(且有定位),或設了 opacity 小於 1、transform 等屬性時,會形成一個自己的堆疊環境,
裡面的子元素的 z-index 只在這個環境裡面比。所以子元素寫 z-index: 9999 也蓋不過父元素外面一個 z-index 比父元素大的元素。
遇到「z-index 設多大都沒用」,先檢查它的祖先是不是形成了堆疊環境。
小測驗:fixed、sticky 與 z-index
對一個沒有設定 position 的 <div> 寫 z-index: 100;,它還是被別的元素蓋住。原因是?
寫了 .header { position: sticky; },捲動時頁首沒有黏住。最可能少了什麼?
頁首改成 position: fixed; top: 0; 之後,網頁的第一段內容被頁首蓋住。合適的修正是?
兩個重疊的方塊都是 position: relative,都沒有設 z-index。哪一個在上面?
重點整理
- 一般流:區塊從上到下、行內從左到右,彼此不重疊;
static是預設值,top、left 與 z-index 都沒有作用。 relative:相對於自己原本的位置位移,原本的空間保留。absolute:離開一般流,相對於最近一個 position 不是 static 的祖先;常見寫法是父層 relative、子層 absolute。fixed:相對於視窗,捲動也不動;記得替被蓋住的內容留空間。sticky:平常在一般流裡,捲到門檻(一定要寫 top 等)時黏住,只在父元素範圍內有效。z-index數字大的在上面,只對有定位的元素(與 Flex、Grid 項目)有效;它只在同一個堆疊環境裡比較。
下一課講溢位、顯示與浮動:內容比盒子大時怎麼處理、隱藏元素的幾種方式,以及舊時代用 float 排版的方法。