CSS必知必會:從z-index到層疊上下文

一個小問題

實現一個按鈕以下圖:css

設計圖

首先能夠想到用box-shadow,可是很遺憾,設計稿是用了兩個圓角矩形疊加的,下面的是純色背景,上面的是一個半透明彩色徑向漸變,造成的效果不是吸管取色能hold住的。因此仍是老實按照設計稿來實現。給按鈕加個after僞元素好了。html

codepen.io/pujiaxun/pe…web

把上面的問題抽象一下:瀏覽器

.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的背景之間。ide

按照我以前錯誤的理解,我想給C加一個z-index: -1,就可讓C在其父元素B的背景後面。然而並無。甚至能夠說我徹底理解錯了…wordpress

層疊順序(Stacking Order)

怎麼解決?先介紹一個概念——層疊上下文(stacking context)。相似BFC,一個比較抽象的概念。根據設定,在一個具體的層疊上下文中,瀏覽器會依據層疊順序規則,對上下文中的全部元素進行排序。學習

首先在瞭解這些名詞以前,咱們靠着豐富的CSS經驗能夠猜到,一個子元素,默認會顯示在父元素的背景之上。廢話啦,要不還能叫背景嗎?以下圖,綠色子元素在灰色父元素之上,也能夠說是在Z軸上更靠近用戶的位置。flex

再稍微有點經驗的能夠發現,inline/inline-block的元素比block元素更高一點。以下圖,綠色元素是inline-block,紫色(?玫紅色?)元素是利用負margin向上偏移的block。但做爲文檔流的後來者,理當後來居上,卻被擋在了下面。ui

這時候我要掏出一張層疊順序天梯圖了spa

簡而言之,若是你們都是普通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:

  1. 根元素 (HTML)
  2. z-index 值不爲 "auto"的 絕對/相對定位
  3. 一個 z-index 值不爲 "auto"的 flex 項目 (flex item),即:父元素 display: flex|inline-flex
  4. opacity 屬性值小於 1 的元素(參考 the specification for opacity)
  5. transform 屬性值不爲 "none"的元素
  6. mix-blend-mode 屬性值不爲 "normal"的元素
  7. filter值不爲「none」的元素
  8. perspective值不爲「none」的元素
  9. isolation 屬性被設置爲 "isolate"的元素
  10. position: fixed
  11. 在 will-change 中指定了任意 CSS 屬性,即使你沒有直接指定這些屬性的值
  12. -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段位高,就被擋住了。

總結

三類層疊上下文

  1. HTML根元素天生具備層疊上下文,稱之爲「根層疊上下文」。
  2. z-index值爲數值的定位元素的「傳統層疊上下文」。
  3. 其餘CSS3屬性,參見MDN

層疊排序規則

  1. 一個元素A向上查找最近的層疊上下文B,A只須要和B的後代元素進行比較
  2. 一個層疊上下文自己和別的元素比較順序時,看做一個總體,內部不需考慮
  3. 層疊級別優先,同級別的遵循文檔流後來居上原則。

z-index不必定是必要的

學完這麼多,其實會發現不少時候不須要用z-index,好比憑經驗就知道,絕對定位的元素通常會更高,再好比如今學到了inline元素也很厲害。更不要說z-index:1000000000這種操做了,固然,避免z-index的濫用又能夠是另外一個話題了。

就這麼簡單嗎

凡事涉及到瀏覽器的具體實現,它就很差玩了。各家瀏覽器會有各類刁鑽的差別,好在大部分使用還比較正常,可能配合上position: fixed、各類transform、filter、: hover僞類等,我只能說 good luck have fun~

參考文檔

深刻理解CSS中的層疊上下文和層疊順序

層疊上下文_MDN

相關文章
相關標籤/搜索