1. 程式人生 > >桌子與堆疊上下文

桌子與堆疊上下文

桌子與堆疊上下文

  或許有人會問,桌子與堆疊上下文有什麼關係?我想說一句很欠揍的話,它們沒什麼關係。
  那我為什麼要取個這樣的標題呢?因為你們都認識桌子,卻不一定能認出來堆疊上下文。但是我如果問你什麼是桌子,你怎麼說?它並沒有一個明確的定義,但是你看到一個東西,就可以知道它是不是桌子。同樣的,你在mdn上搜索堆疊上下文(層疊上下文),你看到的是“文件中的層疊上下文由滿足以下任意一個條件的元素形成”。由…形成,嗯。如果它出現了,你就知道這就是堆疊上下文,但是別人問你什麼是堆疊上下文的時候,你怎麼說?
  好了對標題的解釋就到這裡,我們進入正題。

堆疊順序

  首先要了解的不是堆疊上下文,而是堆疊順序。堆疊順序是基礎。
1. 如果你給一個元素加了border和background,那麼它們會不會有層疊關係呢?答案是有的。如果你給border加一個半透明的顏色,你就會發現background是在border下邊的。所以我們的第一個層疊順序就出來了。background < border


2. 在div有巢狀的時候,誰在上邊呢?答案是div在上邊。你可以看這個示例,我們可以看見,class為child的div把parent的border給擋住了。由此我們可以知道div是在border上邊的。也就是border < div
3. 那float元素呢?我們知道給元素加float之後它會浮起來,那它會浮到所有的元素上邊嗎?這裡我們可以看到,如果給紫色的div加了float的話,本該在它下邊的並且蓋住了它20px的紅色的div就跑到了它的後邊。所以事實是加了float的元素會在正常的div上邊。div < float
  但是這裡有一點需要注意一些,浮動元素裡邊如果有文字的話,這個文字是不會比它外邊的文字高的。
具體可以看這裡。

4. 瞭解一些前端歷史的可能會知道,float元素的出現最初是為了圖文混排的,就是讓文字環繞著圖片顯示。而圖片在顯示的時候往往處於一個次要的位置,文字才是焦點。這裡的文字,就包括了inline元素和inline-block元素等。這裡我們給紫色的div加上display: inline-block之後,它就蓋在了紅色div的上方。所以我們知道float元素 < 內聯元素
5. 再往後,position: absoluteposition: relative的元素都脫離了文件流,那它們和內聯元素的層疊關係是什麼樣的呢?答案是絕對定位和相對定位的元素會優先於inline元素顯示在使用者面前。並且這絕對定位和相對定位對層疊順序的影響相同。你可以自己試一試。所以內聯元素 < 絕對/相對定位

6. 最後一個東西,z-index。z-index是一個神奇的屬性。由上邊的分析我們知道如果沒有其他東西影響的話,預設的顯示順序由底層到最高層應該是background < border < div < float元素 < 內聯元素 < 絕對/相對定位元素。但是z-index的加入,讓這一切有了小小的意外。
  首先,z-index元素只對指定了position屬性的元素有效。也就是說,它不影響其它的,隻影響上邊我們說的最高層級的東西。如果z-index為負,那麼它會在background下邊,如果為正,那麼它在正常的絕對/相對定位元素上邊,如果為0或者是auto,那麼就在絕對/相對定位元素本該待的地方。
  但是,這是建立在你的父元素沒有被定位的情況下。如果你的父元素被定位了並且z-index不等於auto的話,那麼就運算元元素的z-index為-1,它也會在父元素的background和border上邊。你可以自己試一試

堆疊上下文

  好了,堆疊順序解決了。現在可以討論另一個問題了——堆疊上下文。
  堆疊上下文影響的只是z-index,在上邊我們討論堆疊順序的時候,關於負的z-index有一個奇怪的現象,如果元素的爸爸加了position: relative並設定了不為auto的z-index值的時候,這個z-index為負的元素就會跑到background和border的上邊。為什麼呢?因為堆疊上下文。
  這裡是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"的元素

  其中第二條,就是我們上邊接觸到的了。
  還是拿桌子做一個不恰當的比喻吧!本來兒子元素和爸爸元素都在同一張桌子(根元素html)上,但是我們給父元素加了position: relative; z-index: -1;此時形成了層疊上下文,父元素就形成了一個單獨的桌子,子元素在父元素這張桌子上。而background是這張桌子的顏色,border是這張桌子的邊沿,子元素既然是在父元素這張桌子上,那就不會說把桌子挖開把子元素埋進去。唔,姑且這樣理解吧!
  由此的話還有一些其它的東西也就比較好解釋了。比如下邊的程式碼
html:

<div class="parent1"><div class="child1"></div></div>
<div class="parent2"><div class="child2"></div></div>

css:

.parent1{
  width: 150px;
  height: 150px;
  background-color: green;
  position: relative;
  z-index: 1;
}
.child1{
  width: 100px;
  height: 100px;
  background-color: red;
  position: absolute;
  z-index: 2;
}
.parent2{
  width: 150px;
  height: 150px;
  background-color: orange;
  margin-top: -100px;
  position: relative;
  z-index: 1;
}
.child2{
  width: 100px;
  height: 100px;
  background-color: blue;
  position: absolute;
  z-index: 0;
}

  我們可以看到,parent1和parent2的z-index是相等的,parent2把parent1覆蓋了。child1的z-index大於child2的z-index,child2依然把child1覆蓋了。為什麼?
如果你知道一個常識性的知識點,並且聯想到我的桌子理論,那就很容易解釋了。
  這個常識性的知識點是,如果兩個元素層級關係相同,那麼寫在後邊的會覆蓋寫在前邊的。這個我相信所有了解過css的都深有體會。那麼現在的結果就是,parent2在parent1後邊出現,所以parent2理所當然的在parent1前邊。現在還有一個問題,為什麼child1的z-index大於child2的z-index,但是child2會覆蓋child1呢?
  答案也很簡單。想想摞在一起的桌子。html是一張桌子,加了定位和z-index的幾個元素都是單獨的桌子。html上邊比較一下parent1和parent2看誰先摞上去誰後摞上去。答案是parent1先。parent1裡邊的元素又開始比較了,看是child1先摞上去還是其它的元素先上去,答案是沒有其它的元素,只有一個child1,所以先把child1給壓上去了。這才到了parent2,以及parent2裡邊的child2。因為child1和child2根本就不是一張桌子上的東西,所以它們並沒有可比性。