Web 組件化中如何管理 z-index

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!css

z-index 屬性,儘管已經寫了這麼多,仍然被普遍地誤解和錯誤地處理。在複雜的單頁web應用程序中堆積問題可能會成爲一個主要問題。然而,堅持一些原則,咱們能夠很容易地避免這些問題。html

若是你有過任何複雜的Web UI開發,那麼你可能有些時候會瘋狂地嘗試將元素的 z-index 設置爲上千或者很大的一個數,卻發現它不能幫助你把它放在其餘元素之上,這些元素的z-index更低,甚至根本沒有定義。前端

爲何會這樣?更重要的是,如何避免這些問題?git

在本文中,我將講述z-index其實是什麼,以及如何中止猜想它是否適用於任何特定的狀況,並開始像對待任何其餘方便的工具同樣對待它。github

層疊上下文的層次結構web

若是你把網頁想象成三維的,那麼z-index是定義了一個元素的z座標(也稱爲它的層疊順序)屬性:值越大,元素離觀察者越近。你也能夠將它看做是影響繪製順序的屬性,由於屏幕是由像素組成的二維網格。所以,z-index 值越大,元素在頁面上繪製的時間就越晚。segmentfault

然而,一個主要的複雜因素是 z-index 值空間不平 - 它是分層的。 元素能夠建立層疊上下文,該上下文成爲其後代的z-index值的根。 接着經過一個例子來解釋層疊上下文的概念。架構

文檔正文有五個div節點:div1div2div3div1-1div2-1。 它們都是絕對定位的而且彼此重疊。 div1-1div1的子節點,div2-1div2的子節點。ide

html

<body>
  <div class="div1">
    <strong>div1</strong>
    <br/>
    (z-index: auto)
    <div class="div1-1">
      <strong>div1-1</strong>
      <br/>
      (z-index: 10)
    </div>
  </div>
  <div class="div2">
    <strong>div2</strong>
    <br/>
    (z-index: 1)
    <div class="div2-1">
      <strong>div2-1</strong>
      <br/>
      (z-index: 10)
    </div>
  </div>
  <div class="div3">
    <strong>div3</strong>
    <br/>
    (z-index: 2)
  </div>
</body>

css

div {
  box-sizing: border-box;
  border: 1px solid black;
  padding: 5px;
  position: absolute;
  font-family: Verdana;
}

.div1, .div2, .div3 {
  width: 500px;
  height: 200px;
}

.div1-1, .div2-1 {
  width: 200px;
  height: 150px;
}

.div1 {
  left: 10px;
  top: 10px;
  background-color: rgba(220, 220, 170, 0.9);
}

.div1-1 {
  left: 250px;
  top: 30px;
  background-color: rgba(220, 170, 170, 0.9);

  z-index: 10;
}

.div2 {
  left: 20px;
  top: 90px;
  background-color: rgba(220, 170, 220, 0.9);

  z-index: 1;
}

.div2-1 {
  left: 120px;
  top: 30px;
  background-color: rgba(170, 220, 170, 0.9);

  z-index: 10;
}

.div3 {
  left: 30px;
  top: 170px;
  background-color: rgba(170, 220, 220, 0.9);

  z-index: 2;
}

clipboard.png

接着咱們解釋咱們所看到的現象。這裏有詳細繪製順序,但這裏咱們只須要比較兩件事。工具

  • z-index 值

    若是元素具備更高的z-index,則繪製會比值小的晚。

  • 樣式資源順序

    若是 z-index 值相同,那麼在樣式文件中出現越靠後,繪製越晩。

所以,根據咱們的 CSS 文件,若是咱們不考慮層疊上下文,順序應該以下:

  • div1
  • div2
  • div3
  • div1-1
  • div2-1

注意,div2-1 實際上位於 div3 後面的,爲何會這樣?

注意:z-index 值爲 auto 不會建立一個 層疊上下文

若是一個元素要建立一個層疊上下文,它會爲其子元素的 z-index值建立一個地基或者局部框,所以在肯定繪製順序時,它們永遠不會與層疊上下文以外的任何內容進行比較。 換句話說,當一個建立層疊上下文的元素被繪製時,這個元素下的的全部子元素都在它以後和它的任何兄弟以前繪製。

回到示例,body 後代div的實際繪製順序是

  • div1
  • div2
  • div3
  • div1-1

注意列表中沒有 div2-1,它是div2的一個子元素,它建立了一個層疊上下文(由於它是一個絕對定位的元素,除了auto的默認值以外,它還有z-index),因此它是在div2以後繪製的,但在div3以前。

div1 沒有建立層疊上下文,由於它的z-index 值爲 auto,因此在div2div3以後繪製div1-1(div1的子元素,由於div1-1z-index 值爲 10 大於 div2div3

若是你沒有看懂,請不要擔憂。如下是一些資源能夠更好地解釋這些概念:

然而,本文的重點是,當頁面由數十個和數百個組件組成時,如何處理z-index,每一個組件均可能定義了z-index的子組件。

關於z-index 最流行的一篇文章建議將全部z-index值分組在一個地方,可是若是這些值不屬於相同的層疊上下文(在大型應用程序中可能不容易實現),那麼比較這些值就沒有意義了。

這裏一個例子。 假設咱們有一個包含 headermain 部分的頁面。 因爲某種緣由,main 裏面內容樣式必須設置:position: relativez-index: 1

// html
<div class="header">
  Header
</div>

<div class="main">
  Main Content
</div>


// css
body {
  margin: 0;
  font-family: Verdana;
  text-align: center;
  background-color: white;
}

div {
  display: flex;
  align-items: center;
  justify-content: center;
}

.header {
  background-color: rgba(255, 255, 220, 0.5);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
}

.main {
  background-color: rgba(220, 255, 255, 0.5);
  font-size: 10vmin;
  height: 70vh;
  position: relative;
  z-index: 1;
}

運行效果:

clipboard.png

查看示例

在這裏使用的是組件體系結構,因此根組件和每一個子組件的 CSS 都是在專用部分中定義的。實際上,組件將駐留在單獨的文件中,而且標記將使用你選擇的 JavaScript 庫(如React)生成,但出於演示目的,將全部內容放在一塊兒也沒有問題。

如今,假設咱們的任務是在header中建立一個下拉菜單。固然,它必須位於main上面,因此咱們給它一個z-index:10

// html
<div class="header">
  Header
  <div class="overlay">
    Overlay
  </div>
</div>

<div class="main">
  Main Content
</div>


// css
body {
  margin: 0;
  font-family: Verdana;
  text-align: center;
  background-color: white;
}

div {
  display: flex;
  align-items: center;
  justify-content: center;
}

.header {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
}

.overlay {
  position: absolute;
  z-index: 10;
  background-color: rgba(255, 220, 255, 0.8);
  left: 10vw;
  top: 25vh;
  width: 50vw;
  height: 50vh;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}

.overlay::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 100%;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-right: 10px solid transparent;
  border-left: 10px solid transparent;
  border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}

.main {
  background-color: rgba(220, 255, 255, 0.8);
  font-size: 10vmin;
  height: 70vh;
  position: relative;
  z-index: 1;
}

運行效果:

clipboard.png

查看示例

如今,幾個月後,爲了讓一些不相關的東西更好地工做,咱們須要在 header 樣式多加一個 transform: translateZ(0)

// 部分代碼
.header {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
  transform: translateZ(0);
}

如你所見,佈局現已被打破。 在沒有z-index規則的狀況下,z-index:1的元素位於具備z-index:10的元素的頂部。 緣由是header使用transform屬性 它建立了一個層疊上下文,默認會有本身的z-index,值爲0 低於 mainz-index (1)

解決方案很簡單:給header一個z-index值爲2

// 部分代碼
.header {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
  transform: translateZ(0);
  z-index: 2;
}

運行效果:

clipboard.png

查看示例

問題是,若是咱們在組件內的組件中有組件,每一個組件具備不一樣的z-index元素,咱們應該怎麼作到這個解決方案? 咱們如何確保更改 header 的z-index不會破壞其餘任何內容?

答案是須要一種協定,消除了猜想的須要,它是這樣的:更改組件內的z-index應該隻影響該組件,而不影響其餘任何東西。換句話說,在處理某個CSS文件中的z-index值時,理想狀況下咱們應該只關心該文件中的其餘值。

實現它很容易。 咱們應該簡單地確保每一個組件的根建立層疊上下文。 最簡單的方法是爲它提供鵲確切的 positionz-index 的值,而不是使用默認它們本身的默認值。

下面是構建應用程序的一種方法。它使用的元素比前一個多,可是相對額外的DOM元素相關聯的計算來講是很划算的,節省開發人員在處理層疊上下文問題時間。

// html
<!-- root component starts -->
<div class="root__container">

  <div class="root__header">
    
    <!-- header component starts -->
    
    <div class="header__container">
      Header
      <div class="header__overlay">
        Overlay
      </div>
    </div>
    
    <!-- header component ends -->
    
  </div>

  <div class="root__main">
    
    <!-- main component starts -->
    
    <div class="main__container">
      Main Content
    </div>
    
    <!-- main component ends -->
    
  </div>

</div>

<!-- root component ends -->


// css
body {
  margin: 0;
  font-family: Verdana;
  text-align: center;
  background-color: white;
}



/* Root styles */

.root__container {
  position: relative;
  z-index: 0;
}

.root__header {
  position: relative;
  z-index: 2;
}

.root__main {
  position: relative;
  z-index: 1;
}



/* Header styles */

.header__container {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
  transform: translateZ(0);
  
  display: flex;
  align-items: center;
  justify-content: center;

  z-index: 0;
}

.header__overlay {
  position: absolute;
  z-index: 1;
  background-color: rgba(255, 220, 255, 0.8);
  left: 10vw;
  top: 25vh;
  width: 50vw;
  height: 50vh;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
  
  display: flex;
  align-items: center;
  justify-content: center;
}

.header__overlay::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 100%;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-right: 10px solid transparent;
  border-left: 10px solid transparent;
  border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}

/* Main section styles */

.main__container {
  background-color: rgba(220, 255, 255, 0.8);
  font-size: 10vmin;
  height: 70vh;
  
  display: flex;
  align-items: center;
  justify-content: center;
  
  position: relative;
  z-index: 0;
}

運行效果:

clipboard.png

查看示例

  • header__containermain__container 都有 position: relativez-index: 0
  • header overlay現有z-index: 1(咱們不須要很大的值,由於它只會與 header 中的其餘元素進行比較)
  • root__header 現有z-index: 2,而 root__main 保持其z-index: 1,這就是兩兄弟正確層疊的緣由

(還要注意,二者都有position: relative,由於 z-index 不適用於 position:static的元素。)

若是咱們如今查看 header 代碼,咱們會注意到咱們能夠徹底從containeroverlay 層中刪除z-index屬性,由於overlay 層是那裏惟必定位的元素。 一樣,main container 上不須要z-index。 這樣分類最大好處:在查看z-index時,只注重組件自己,而不是它的上下文。

// 刪除上述所說的 z-index
body {
  margin: 0;
  font-family: Verdana;
  text-align: center;
  background-color: white;
}



/* Root styles */

.root__container {
  position: relative;
  z-index: 0;
}

.root__header {
  position: relative;
  z-index: 2;
}

.root__main {
  position: relative;
  z-index: 1;
}



/* Header styles */

.header__container {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
  transform: translateZ(0);
  
  display: flex;
  align-items: center;
  justify-content: center;
}

.header__overlay {
  position: absolute;
  background-color: rgba(255, 220, 255, 0.8);
  left: 10vw;
  top: 25vh;
  width: 50vw;
  height: 50vh;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
  
  display: flex;
  align-items: center;
  justify-content: center;
}

.header__overlay::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 100%;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-right: 10px solid transparent;
  border-left: 10px solid transparent;
  border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}



/* Main section styles */

.main__container {
  background-color: rgba(220, 255, 255, 0.8);
  font-size: 10vmin;
  height: 70vh;
  
  display: flex;
  align-items: center;
  justify-content: center;
}

運行效果:

clipboard.png

這種架構並不是沒有缺點。 它以犧牲一些靈活性爲代價使應用程序更具可預測性。 例如,你將沒法在headermain section內建立此類疊加層:

// html
<div class="header">
  Header
  <div class="header-overlay">
    Header Overlay
  </div>
</div>

<div class="main">
  Main Content
  <div class="main-overlay">
    Main Overlay
  </div>
</div>


// css
body {
  margin: 0;
  font-family: Verdana;
  text-align: center;
  background-color: white;
}

div {
  display: flex;
  align-items: center;
  justify-content: center;
}

.header {
  background-color: rgba(255, 255, 220, 0.8);
  font-size: 10vmin;
  position: relative;
  height: 30vh;
}

.header-overlay {
  position: absolute;
  z-index: 10;
  background-color: rgba(255, 220, 255, 0.8);
  left: 2vw;
  top: 25vh;
  width: 47vw;
  height: 50vh;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}

.header-overlay::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 100%;
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-right: 10px solid transparent;
  border-left: 10px solid transparent;
  border-bottom: 10px solid rgba(255, 220, 255, 0.8);
}

.main {
  background-color: rgba(220, 255, 255, 0.8);
  font-size: 10vmin;
  height: 70vh;
  position: relative;
}

.main-overlay {
  position: absolute;
  z-index: 10;
  background-color: rgba(255, 255, 255, 0.8);
  left: 51vw;
  bottom: 40vh;
  width: 47vw;
  height: 50vh;
  filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3));
}

.main-overlay::after {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 100%;
  width: 0;
  height: 0;
  border-top: 10px solid rgba(255, 255, 255, 0.8);
  border-right: 10px solid transparent;
  border-left: 10px solid transparent;
  border-bottom: 10px solid transparent;
}

clipboard.png

查看示例

然而,根據個人經驗,這不多是一個問題。 你可使用 main section 中的 overlay 層向下而不是向上,以使其不與header相交。 或者,若是你真的須要它,你能夠在正文的末尾註入疊加HTML 並給它一個大的 z-index 值(「大」是比頂層其餘部分更大)。

再次說明

  • 經過使每一個組件的根成爲層疊上下文,根據元素的z-index值隔離組件;
  • 若是組件中的元素不須要除auto以外的z-index值,則沒必要執行此操做;
  • 在組件的 CSS 文件中,能夠以你喜歡的方式維護z索引值。它多是連續的值,或者你能夠給它們一個10的步長,或者你可使用變量——這都取決於你的項目約定和組件的大小。最好只將z-index分配給同級元素。不然,你可能會無心中在一個組件中引入更多的層疊上下文。
  • 調試變得容易。找到兩個沒有正確層疊的元素的第一個祖先組件,並根據須要更改該組件中的z-index

你的點贊是我持續分享好東西的動力,歡迎點贊!

歡迎加入前端你們庭,裏面會常常分享一些技術資源。

clipboard.png

相關文章
相關標籤/搜索