一個小問題
實現一個按鈕如下圖:
首先可以想到用box-shadow,但是很遺憾,設計稿是用了兩個圓角矩形疊加的,下面的是純色背景,上面的是一個半透明彩色徑向漸變,形成的效果不是吸管取色能hold住的。所以還是老實按照設計稿來實現。給按鈕加個after偽元素好了。
把上面的問題抽象一下:
.grandfather A
.father B
.son C
複製程式碼
.grandfather {
width: 300px;
height: 200px;
background-color: #999;
}
.father {
width: 200px;
height: 150px;
background-color: #acc;
position: relative;
}
.son {
width: 100px;
height: 100px;
background-color: #c88;
position: absolute;
bottom: -40px;
}
複製程式碼
其實目標就是讓子元素C(即按鈕的純色背景)介於父元素B的背景與爺爺元素A的背景之間。
按照我之前錯誤的理解,我想給C加一個z-index: -1
,就可以讓C在其父元素B的背景後面。然而並沒有。甚至可以說我完全理解錯了…
層疊順序(Stacking Order)
怎麼解決?先介紹一個概念——層疊上下文(stacking context)。類似BFC,一個比較抽象的概念。根據設定,在一個具體的層疊上下文中,瀏覽器會依據層疊順序規則,對上下文中的所有元素進行排序。
首先在瞭解這些名詞之前,我們靠著豐富的CSS經驗可以猜到,一個子元素,預設會顯示在父元素的背景之上。廢話啦,要不還能叫背景嗎?如下圖,綠色子元素在灰色父元素之上,也可以說是在Z軸上更靠近使用者的位置。
再稍微有點經驗的可以發現,inline/inline-block的元素比block元素更高一點。如下圖,綠色元素是inline-block,紫色(?玫紅色?)元素是利用負margin向上偏移的block。但作為文件流的後來者,理當後來居上,卻被擋在了下面。
這時候我要掏出一張層疊順序天梯圖了
簡而言之,如果大家都是普通block元素,按照文件流後來居上,符合常識。但如果我段位比你block元素更高,比如我是inline-block選手,那你還木有資格和我PK,輪不到你和我比什麼文件流順序,甚至都不用看你爹是誰。比如下圖
按照直覺,紫色元素在綠色元素的下面,那紫色元素的內容B就應該被擋住不顯示。然而並沒有,B作為一個文字節點,預設是inline,在天梯排名中比綠色block更高,儘管它是叔叔元素(原諒我這麼隨便的起名……)
那怎麼用z-index呢
這又要說回層疊上下文了。
一個層疊上下文可以理解成是一種特殊段位,它可以和z-index值組合,形成一個具體的段位。 我寫了一個更詳細點的圖(CodePen地址)
比如一個z-index為-1的層疊上下文,它的段位就在藍色的級別。
層疊上下文
那到底**什麼是層疊上下文啊?
就像BFC,當一個元素在某些特殊情況下,就會變成一個層疊上下文。舉個簡單的例子,元素X絕對定位,並且z-index: 1000
,它就形成了一個層疊上下文,其段位(層疊順序)相當高。
而一個層疊上下文一旦形成,整個事情會有一些變化。元素X下的所有子元素將不再參與元素X以外的級別PK,只會在這個最近的上下文中去比較。 (還記得剛剛那個欺負叔叔元素的B嗎?)
除了絕對定位配合z-index屬性,還有什麼辦法形成層疊上下文呢?參考MDN:
- 根元素 (HTML)
- z-index 值不為 "auto"的 絕對/相對定位
- 一個 z-index 值不為 "auto"的 flex 專案 (flex item),即:父元素 display: flex|inline-flex
- opacity 屬性值小於 1 的元素(參考 the specification for opacity)
- transform 屬性值不為 "none"的元素
- mix-blend-mode 屬性值不為 "normal"的元素
- filter值不為“none”的元素
- perspective值不為“none”的元素
- isolation 屬性被設定為 "isolate"的元素
- position: fixed
- 在 will-change 中指定了任意 CSS 屬性,即便你沒有直接指定這些屬性的值
- -webkit-overflow-scrolling 屬性被設定 "touch"的元素
看起來與合成層(Compositing Layer)的形成條件,有那麼點異曲同工。
等一哈
有個小問題,這層疊順序說的這麼好聽,好像有個地方漏了。一個絕對定位的元素,似乎也是高於普通兄弟元素(正常文件流中)的,即使沒有設定z-index。它不是層疊上下文啊?屬於什麼段位呢?
定位元素屬於 z-index: 0/auto 段位。 但需要解釋一下下,首先這個段位,W3的定義是
the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
當你使用了非static定位屬性,例如absolute定位,就會自動生成z-index: auto。還有其他例如transform形成的層疊上下文,如果沒有指定z-index,也預設變為auto,和定位元素同段位,遵循後來居上原則。
那一個普通元素,直接寫個z-index: auto會怎麼樣呢?
可以,你很熱愛學習。答案是沒用的,因為它既不是“stacking contexts”,也不是“positioned descendants”。
一個層疊上下文的z-index為auto時,可以當作0看待。但是定位元素的z-index為auto時,不能和0等同,因為它不是層疊上下文。還記得嗎,定位元素配合數值z-index會形成層疊上下文。它只是定義中的一種特殊情況。
回到需求
.grandfather A
.father B
.son C
複製程式碼
.grandfather {
position: relative;
z-index: 0;
}
.father {
position: relative;
}
.son {
position: absolute;
bottom: -40px;
z-index: -1;
}
複製程式碼
給grandfather元素設定相對定位,以及z-index: 0
,即可將grandfather生成一個層疊上下文,此時father元素相當於這個上下文中的z-index: 0/auto
段位,再給son元素設定一個負數z-index,即可形成一個 z-index: -1
級別的層疊上下文,理應排在father元素後面,在grandfather的背景前面,達成理想。
而在最一開始,我以為只要給son設定一個z-index: -1
,結果變成了下圖:
son都被grandfather擋住了,因為son的父元素father、grandfather,都不是層疊上下文,他們共同處在更上級的層疊上下文中(比如html根元素)。所以son還要和grandfather去比較段位,結果son是-1級層疊上下文,沒有普通block的grandfather段位高,就被擋住了。
總結
三類層疊上下文
- HTML根元素天生具有層疊上下文,稱之為“根層疊上下文”。
- z-index值為數值的定位元素的“傳統層疊上下文”。
- 其他CSS3屬性,參見MDN。
層疊排序規則
- 一個元素A向上查詢最近的層疊上下文B,A只需要和B的後代元素進行比較
- 一個層疊上下文字身和別的元素比較順序時,看作一個整體,內部不需考慮
- 層疊級別優先,同級別的遵循文件流後來居上原則。
z-index不一定是必要的
學完這麼多,其實會發現很多時候不需要用z-index,比如憑經驗就知道,絕對定位的元素一般會更高,再比如現在學到了inline元素也很厲害。更不要說z-index:1000000000
這種操作了,當然,避免z-index的濫用又可以是另一個話題了。
就這麼簡單嗎
凡事涉及到瀏覽器的具體實現,它就不好玩了。各家瀏覽器會有各種刁鑽的差異,好在大部分使用還比較正常,可能配合上position: fixed
、各種transform、filter、: hover偽類等,我只能說 good luck have fun~