使用 CSS nth-child 必須要注意的事情

之前有寫過一篇 利用 :nth-child() 讓網頁展示商品時左右對齊 的文章,裏面有稍微提到:nth-child()的使用方法。

其實:nth-child()好用的地方就在於他可以針對每個元素做個別的樣式設定,不用再像以往一樣用程式判斷然後算半天。不過我最近發現,有一些排版,沒辦法直接使用:nth-child()達到我想要的效果,所以最後我還是改用 jQuery 撰寫。如果你常使用:nth-child()寫網頁,不妨可以參考一下唷。

再次引用 W3C 的解釋(請特別記住這句話,接下來的範例,受到這句話的影響非常深刻唷)

:nth-child(n) 選擇器匹配屬於其父元素的第 N 個子元素,不論元素的類型。n可以是數字、關鍵詞或公式。

【範例】規定屬於其父元素的第二個子元素的每個 p 的背景色:p:nth-child(2) { background:#ff0000; }

CSS3 :nth-child() 選擇器 / W3SCHOOL

看完如果似懂非懂的朋友,沒關係,請先記住這句話,下面會再詳細解說。這篇文章先當作大家已經會使用基本的:nth-child()

範例

話不多說,直接上範例,CSS & HTML 語法請看我的 codepen,或參考以下 DEMO:

See the Pen nmlkt by MUKi (@mukiwu) on CodePen.

以上這個網頁結構,分作主課程 (WordPress, CSS, Html) 以及子課程 (install plugin, install theme…等) 兩種;在範例中,我想要做的效果是「當子課程是偶數欄時,背景會變咖啡色」。

左邊是用 CSS 的:nth-child(odd)語法,右邊則是用 jQuery 的:odd selector,大家可以看到最後呈現的兩種效果不太一樣。(是的,你沒看錯,odd 是奇數不是偶數,為什麼我會用 odd 而不用 even,後面會解釋)

就常理而言,應該是右邊的「Use jQuery(:odd)」會比較好閱讀,左邊的欄位變色感覺有些不連貫。如果你有看 codepen 的語法,應該會發現我都是使用:odd來控制,那為什麼計算的結果不一樣呢?難道是 CSS 跟 jQuery 對 odd 的計算方式不同嗎?

其實可以說是,也不是。事實上,這跟 HTML DOM 有比較大的關聯。

版面解析

讓我們先來拆解這個 HTML 版面。這是一個課程清單表,主課程底下會有很多子課程,所以用類似「合併儲存格」的功能,把最前面的主課程欄位合併,這樣在看課程的時候較一目了然。因此,我是這麼寫的:

<div class="class-wrap">
  <div class="class">Wordpress<br />(Wednesday)</div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">W</div>
    </div>
    <div class="course">install plugin</div>
    <div class="time">19:00 ~ 21:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">W</div>
    </div>
    <div class="course">install theme</div>
    <div class="time">14:00 ~ 18:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">W</div>
    </div>
    <div class="course">post your first atricle</div>
    <div class="time">18:00 ~ 20:00</div>
    <div class="index"></div>
  </div>
</div>

<div class="class-wrap">
  <div class="class">CSS<br />(Saturday)</div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">C</div>
    </div>
    <div class="course">Basic knowledge</div>
    <div class="time">19:00 ~ 21:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">C</div>
    </div>
    <div class="course">CSS selectors explain</div>
    <div class="time">15:00 ~ 20:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">C</div>
    </div>
    <div class="course">CSS Hack</div>
    <div class="time">18:00 ~ 22:00</div>
    <div class="index"></div>
  </div>
</div>

<div class="class-wrap">
  <div class="class">HTML<br />(Friday)</div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">H</div>
    </div>
    <div class="course">What is HTML</div>
    <div class="time">10:00 ~ 12:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">H</div>
    </div>
    <div class="course">HTML attributes</div>
    <div class="time">13:00 ~ 15:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">H</div>
    </div>
    <div class="course">write first Homepage</div>
    <div class="time">15:30 ~ 17:00</div>
    <div class="index"></div>
  </div>
  <div class="content">
    <div class="icon">
      <div class="icon-border">H</div>
    </div>
    <div class="course">Exercise</div>
    <div class="time">18:00 ~ 21:00</div>
    <div class="index"></div>
  </div>
</div>

我用.class-wrap包住最大的課程類別,再用.content包住子課程。所以簡化版的 HTML 會長這樣:

<div class="class-wrap">
  <div class="class">主課程標題 1</div>
  <div class="content">子課程 1</div>
  <div class="content">子課程 2</div>
  <div class="content">子課程 3</div>
</div>

<div class="class-wrap">
  <div class="class">主課程標題 1</div>
  <div class="content">子課程 1</div>
  <div class="content">子課程 2</div>
</div>

接著,回到原本我們想做的網頁效果:「子課程的偶數欄位要變色」。

我們想要變色的 class 是.content,然後是偶數(even) 欄位。假使我們用:nth-child來寫的話,正常毫無疑問會這麼寫:

.content:nth-child(even) {
  background: #A69689;
}

但你會發現,用:nth-child(even)後,居然是奇數欄位變色了!! (也可以直接編輯我的 codepen 看結果唷)

揪竟是為什麼呢!?只能說:nth-child是個非常奇妙的東西,尤其碰到像範例這種複雜的排版,整個計算方式會跟常理認知的不太一樣呢!讓我們來慢慢的、一步步了解吧。


一、先了解:nth-child(n)的初始值是 0 還是 1 ?

:nth-child(n)的初始值是 1 ,依序 1、2、3、4 這樣算下來。因此沒有:nth-child(0)這種東西

二、再了解:nth-child(n)的定義

重點 請把剛才我請你記住的那句話,關於:nth-child(n)的定義,拿出來複習一下::nth-child(n)選擇器匹配屬於其父元素的第 N 個子元素,不論元素的類型」

接著,我們再把剛剛的 CSS code.content:nth-child(even),改成跟定義一樣的句子:.content選擇器,匹配其父元素.class-wrap的第偶數(even)個子元素」

… 好吧,我覺得大家一定看不懂,讓我改用白話文說一遍 (嗚嗚,要講解這一部分好難啊,不要逼我自暴自棄 T^T)

  1. 先找到.content的父元素:.class-wrap
  2. 找出.class-wrap底下的所有子元素,以剛剛的語法範例來看,第 1 個子元素是class="class"主課程標題,第 2 個子元素才是class="content"子課程 1
    <div class="class-wrap">
      <div class="class">主課程標題 1</div>
      <div class="content">子課程 1</div>
      <div class="content">子課程 2</div>
    </div>
  3. 因此,原本我們以為「子課程 1 」會是匹配父元素的第 1 個元素,但因為.content前面還有一個.class,所以「子課程 1 」變成了匹配父元素的第 2 個元素。
  4. 再重新把.content:nth-child(even)拿回來看,其實符合偶數(even)的元素是這些:

這樣大家清楚嗎?因為前面多了一個不是.content的 class,但他也是.class-wrap的子元素阿!所以全部算下來,原本我們以為的偶數會是奇數、奇數會變偶數。因此我的範例語法才會寫:odd而非:even

如果了解,就讓我們繼續進行下去。

三、為什麼 n 的計算不是從 1 到 100 呢?

因為:nth-child(n)是以父元素.class-wrap作為起始點。假設第一個主課程底下有 4 個子課程,他的元素依序計算為 1、2、3、4;但,到了第二個主課程又會重新從 1 開始計算,並不會從 5 繼續數下去。

這就是為什麼我們看到左邊的範例會感覺參差不齊,但如果你把每個.class-wrap都當作一個段落,就會發現他其實偶數的元素還是會變色的。


圖解

那,到底每個子課程的:nth-child(n)是多少?

還是不太清楚的朋友,可以再看看我寫的 codepen:http://codepen.io/mukiwu/full/nmlkt ,然後隨意點選任何子課程的欄位。你會看到如下結果:

如果你點選了任意子課程的欄位,可以在紅底的區塊看到一些數字。

簡單總結:

  • 「Use CSS nth-child(odd)」:以主課程作為區塊,每個主課程結束以後,會重新開始計算。這邊出現的數字是該欄位的:nth-child(n)的 n 值
  • 「Use jQuery(:odd)」:單純計算有多少子課程,不受主課程影響,所以會從 0 算到最大個數。這邊出現的數字是所有子課程的 index 值

以上是兩種寫法主要的差別。

利用 jQuery :odd 撰寫

就如前面所說,為了視覺的平衡,我們幾乎都會採用右邊的表現手法,所以可以改用 jQuery 撰寫:

var contentOdd = $("#jquery-trick .content:odd");
contentOdd.css("background" , "#A69689");
contentOdd.children(".course").css("color","#eee");

只是要特別注意的是,jQuery(":odd")的初始值是 0 不是 1,所以第 1 個子課程是偶數、第 2 個子課程才是奇數… 以此類推。
我們以為偶數的欄位是奇數,以為奇數的欄位是偶數,這樣的情況會再度發生。

總結

如果你會用到像我這樣的排版,直覺的使用CSS nth-child來撰寫的話,也許會碰到同樣的問題。因此,可以嘗試使用 jQuery 解決。如果有可以直接用 CSS 解掉的方法,也歡迎告訴我唷:)

以後,我會將一些寫的較漂亮的範例轉移到 codepen,如果你喜歡我寫的範例,也歡迎到我的 codepen 支持我唷


  • Carl Shih

    謝謝了~!! 剛開始學這個真的容易沒注意到是"父"元素的第幾個!

    • mukiwu

      這個其實很饒舌 XDD,我自己後來看文件的時候也才恍然大悟