CSS秘密花園: 相鄰元素樣式
《 CSS Secrets 》是 @Lea Verou 最新著作,這本書講解了有關于CSS中一些小秘密。是一本CSSer值得一讀的一本書,經過一段時間的閱讀,我、@南北和@彥子一起將在W3cplus發布一系列相關的讀后感,與大家一起分享。
問題
在很多情況下,我們需要給元素的兄弟元素以不同的樣式風格呈現。主要的用例是提高用戶體驗和在大屏幕中不斷增加列表薦。下面是一些使用場景:
郵件列表或類似基于文的列表展示。如果我們只有為數不多的列表項,我們可以單獨展示。隨著列表項的增加,我們可以減少列表的預覽項。當列表的高度大于視窗高時,我們可能會選擇隱藏一些列表項,或者在尾添加一個查看更多的按鈕,讓用戶在不滾動滾動條也能瀏覽列表。
App中的待辦事項列表(to do list),可以給幾個列表項設置大的文本樣式,但隨著列表項數目增加,列表項目的設置較小的字號。
用來顯示顏色的調色板APP。有人可能想隨著顏色數量增加,利用好空間讓顏色控制面板變得更緊湊些,如下圖所示:
隨著顏色的數量增加,逐步縮小控制顏色的埠和減少可用空間。注意,在特殊情況之下,當只有一個顏色時,會隱藏刪除按鈕。
具有多個 <textarea> 的APP,我們每次新增加一個文本域,他就比前一個更小一些(例如 bytesizematters.com )。
然而,針對元素相鄰兄弟元素來選擇目標元素不是簡單的CSS選擇器。例如,當列表總數是 4 個時給列表不同樣式。我們可以使用 :nth-child(4) 來選擇列表的第四個列表項,但這并不是我們所需要的,我們要的是當列表總數為 4 的時候,選擇列表的每一個列表項。
有一個想法就是使用通用選擇符( ~ )和 :nth-child() 選擇器配合使用,例如 li:nth-child(4),li:nth-child(4)~li 。不管列表項總數有多少,只選擇了第四個項和其之后的所有列表項,如下圖所示:
因為沒有“向后選擇器”和選擇元素之前的兄弟元素的選擇器,那么CSS就注定不能選擇到需要的目標元素嗎?我們不應該失去信心,一切皆有可能。
解決方案
有一個特殊案例就是只有一個單獨的列項項目,有一個明顯的解決方案: :only-child 選擇器,這個選擇器就是為這種情況而生的。他是非常有用的,這也是為什么會將其添加到規范中。例如前面提到的顏色調控板APP,當只有一個顏色時,會隱藏刪除按鈕,那么就可以使用 :only-child 選擇器來實現。
li:only-child {
/* 當只有一個列表項時的樣式 */
}
我們在這一節討論的是 :nth-child() 選擇器,但這一切的討論都同樣適用于 :nth-of-type() 選擇器。往往這種類型選擇器更適用,通常情況之下,容器中會有不同類型的兄弟元素,使用這種類型選擇器,我們只要關心一種類型。這里將使用列表元素做為示例討論,但討論中的一切都適用于任何類型元素。
然而, :only-child 相當于 :first-child:last-child ,他是第一項也是最后一項,從邏輯上可以得出結論,他就是唯一的項目。其實, :last-child 就是 :nth-last-child(1) 簡寫。
li:first-child:nth-last-child(1) {
/* 和li:only-child等同 */
}
當然,現在 1 就是一個參數,我們可以根據自己的喜好調整這個參數。你能猜出 li:first-child:nth-last-child(4) 選到的目標是什么?如果你回答,當列表的總數是四個的時候,選擇到 :only-child 的目標元素,你可能太過于樂觀了一點。其實事實并不是這樣,但是離我們正確的選擇目標越來越接近。分別考慮兩個選擇器: :first-child 和 :nth-last-child(4) 。因此,在相同時間從元素第一個子元素開始計數和倒數第四個子元素計數。那么哪些元素滿足這個范圍呢?
正確答案是,只個四個元素的列表的第一個元素被選中,如下圖所示:
這并不是我們想要的結果,但已非常接近我們需要的效果。因為我們在一個列表中選擇到了第一個子元素,我們可以使用通用選擇符( ~ )來選擇第一個子元素后的相鄰兄弟元素。這樣一來,如果列表只有四個列表項時,所有列表項都將被選中。這正是我們試圖要實現的效果:
li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
/* 目標選擇了僅有四個列表項的列表所有列表項 */
}
為了避免代碼的冗長和重復的解決方案,可以使用預處理器,例如SCSS。盡管現有的預處理器語法比圖笨拙:
/* 定義mixin */
@mixin n-items($n) {
&:first-child:nth-last-child(#{$n}),
&:first-child:nth-last-child(#{$n}) ~ & {
@content; }
}
/* 像這樣使用 */
li {
@include n-items(4) {
/* 這里寫樣式 */
}
}
選擇兄弟元素范圍
在大多數實際運用中,我們并不希望瞄準特定數量的列表,而是希望在一個范圍內。有一個小技巧,我們可以使用 :nth-child() 選擇器來指定一個選擇范圍。例如選擇第四個之后的列表項。除了使用簡單的數字作為參數之外,還可以使用 an+b 表達式作為參數(例如: :nth-child(2n+1) ),其中 n 是一個變量,范圍是從 0 到 +∞ (實際上,值在某點不會選擇任何東西,因為我們元素的數量是有限的)。如果我們使用一個表達式 n+b ( a 默認下是 1 ),那么,如果 n 不是一個正整數時可以給我們一個小于 b 值的范圍。因此,表達式 n+b 可以用來第 b 個值為第一個子元素所有子元素。例如: :nth-child(n+4) 選擇除了第一、第二、第三的所有子元素。如下圖所示:
如果你不想為 :nth-* 選擇器做過多的思考,你可以使用一個測試工具,我寫了一個 在線的工具 ,足夠你做測試。
我們可以利用這個功能來選擇列表項是四個或更多的列表子元素,如下圖所示:
在這種情況下,我們可以在 :nth-last-child() 使用 n+4 這個表達式:
li:first-child:nth-last-child(n+4),
li:first-child:nth-last-child(n+4) ~ li {
/* 目標列表選擇最少包含四個列表項 */
}
同樣,表達式 -n+b 可以用來選擇第 b 個元素。因此,選擇僅有四個或更少的列表項的列表的所有子元素時,就可以使用這種表達式。
如上圖所示,我們代碼可以這樣寫:
li:first-child:nth-last-child(-n+4),
li:first-child:nth-last-child(-n+4) ~ li {
/* 選擇列表項最多只有四個的列表所有子元素 */
}
當然,我們可以結合這兩個,但現在的代碼變得更加笨拙。假設我們列表包含 2~6 項的列表所有子元素:
li:first-child:nth-last-child(n+2):nth-last-child(-n+6),
li:first-child:nth-last-child(n+2):nth-last-child(-n+6) ~ li{
/*選擇包含2-6個列表項的列表所有子元素*/
}
來自: http://www.w3cplus.com/css3/css-secrets/styling-by-sibling-count.html