本篇文章帶大家了解CSS中的選擇器,介紹一下簡單選擇器、複合選擇器、複雜選擇器的語法,選擇器優先權。
#我們先了解選擇器的語法,然後再深入了解背後相關的特性。
簡單選擇器
*
div svg|a
tagName
(標籤名) 屬性HTML
、SVG
、MathML
|
,CSS選擇器裡面單豎線是一個命名空間的分隔符,而HTML 裡面命名空間分隔符是冒號:
。然後前面說到的命名空間是需要@namespace 來聲明的,他們是配合使用的,但是這個命名空間的使用不是很頻繁,它的存在只是為了一個完備性考慮,HTML 和SVG當中唯一一個重疊的元素名就只有一個a
.class-name
#
.
開頭的選擇器就是class 選擇器,也是最經典之一.class
只要符合其中一個就可以了#id
# 開頭加上ID 位元選取一個ID[attr=value]
attr=value
,等於前面是屬性名,後面是屬性值~
就表示像class 一樣,可以支援拿空格分隔的值的序列:attr~=value
attr|=value
:hover
:
開頭的,它主要是一些屬性的特殊狀態::before
::
雙冒號開頭的#複合選擇器
首先複合選擇器是以多個簡單選擇器構成的,只要把簡單選擇器挨著寫就變成一個複合選擇器了。它的語意就是我們選取的元素必須同時 match 幾個簡單選擇器,形成了 「與」 的關係。
複雜選擇器
#複合選擇器中間用連接符號就可以變成複雜選擇器了,複雜選擇器是針對一個元素的結構來進行選擇的。
在先前的《實戰中學習瀏覽器運作原理》中也接觸過選擇器的優先權的概念了。這裡我們深入了解一下選擇器優先順序的概念。
簡單選擇器計數
我們從一個案例出發,選擇器優先權是對一個選擇器裡麵包含的所有簡單選擇器進行計數。所以選擇器清單不被視為一個完整的選擇器(也就是逗號分隔的選擇器),因為選擇器清單中間是以逗號分隔開的複雜選擇器來進行簡單選擇器計數的。
範例:
#id div.a#id
specificity
陣列的計數[inline-style個數
,ID 選擇器個數
,class 選擇器個數
,tagName 選擇器個數
]specificity = [0, 2, 1, 1]
N=1000000
,那麼S=2000001000001
#,這個就是這個範例中選擇器的specificity
優先權了像IE 的舊版IE6,因為為了節省記憶體N 取值不夠大,取了一個255 為N 的值,所以就發生了非常好玩的事情,比如說到值我們256 個class 就相當於一個ID。後來我們大部分的瀏覽器都選擇了 65536,基本上就再也沒有發生過超過額度的事情了。因為標準裡面只說採用一個比較大的值就可以,但是我們要考慮記憶體暫用的問題,所以我們會取一個16 進位上比較整的數,一般來說都是256 的整次方(因為256 是剛好是一個位元組)。
#偽類別其實是一類非常多的內容的簡單選擇器。
連結/行為
#:any-link
—— 可以符合任何的超連結:link
—— 還沒造訪過的超連結:link :visited
—— 符合所有被造訪過的超連結:hover
—— 使用者滑鼠放在元素上之後的狀態,之前是只能對超連結生效,但是現在是可以在很多元素中使用了:active
—— 之前也是只對超連結生效的,點擊之後當前的連結就會生效:focus
—— 就是焦點在這個元素中的狀態,一般用於input 標籤,其實任何可以獲得焦點的元素都可以使用:target
—— 連結到目前的目標,這個不是給超連結用的,是給錨點的a
標籤使用的,就是目前的HASH指向了目前的a
標籤的話就會啟動target
偽類別一旦使用了
:link
或:visited
之後,我們就再也無法對這個元素的文字顏色之外的屬性進行更改。為什麼要這樣設計呢?因為一旦我們使用了 layout 相關的屬性,比如說我們給:visited
的尺寸加大一點,它就會影響排班。這樣我們就可以透過 JavaScript 的 API 去取得這個連結是否被存取過了。但是如果我們能獲得連結是否被訪問過了,那麼我們就可以知道用戶訪問過那些網站了,這個對於瀏覽器的安全性來說是一個致命打擊。所以這裡也提醒一下大家,不要以為做一些表現性的東西於安全沒有任何關係,其實安全性是個綜合的考量。 CSS 它也能造成安全漏洞的。
樹結構
#:empty
—— 這個元素是否有子元素:nth-child()
—— 是父元素的第幾個兒子(child):nth-last-child()
— — 於nth-child
一樣,只不過從後往前數#:first-child :last-child :only-child
,這個就會分別配對到整數的形態。因為這個是比較複雜的選擇器,我們就不要在裡面寫過於複雜的表達式了,只用它來處理一下奇偶,逢3個多1個,逢4個多1個等等這種表達式。
##:nth-child
是一個非常複雜的偽類,裡面支援一種文法,比如說可以在括號裡面寫奇偶
event或
odd,也可以寫
4N 1、
3N-1
其實empty 、
nth-last-child影響不是特別大,但邏輯型last-child 的這個關係其實還是影響蠻大的。所以瀏覽在實現這些的時候是做了特別處理的,要嘛就是瀏覽器實現的不是特別好,要嘛就是瀏覽器要耗費更大的效能來實現。所以建議大家盡量避免大量使用這些。
:where :has —— 在CSS Level 4 加入了這兩個非常強大了邏輯型偽類
CSS 偽元素
#一共分為4 種
##::before
::after
::first-line
::before
和### ::after### 是在元素的內容的前後,插入一個偽元素。一旦應用了 before 和 after 的屬性,declaration(宣告)裡面就可以寫一個叫做 ###content### 的屬性(一般元素是沒有辦法寫 content 的屬性的)。 ###content### 的屬性就像一個真正的 DOM 元素一樣,可以去產生盒,可以參與後續的排版和渲染了。所以我們可以給他宣告 ###border###、###background###等這樣的屬性。 #########可以理解為:###偽元素在介面上加入了一個不存在的元素。 ######
::first-line
和 ::first-letter
的機制就不一樣了。這兩個其實原本就存在 content 之中。他們顧名思義就是 選中“第一行” 和選中 “第一個字母”。它們 不是一個不存在的元素,是把一部分的文字括了起來讓我們可以對它做一些處理。
before 和after
在我們概念裡,我們可以認為帶有before 偽元素的選擇器,會給他實際選取的元素的內容前面增加了一個元素,我們只需要透過他的content 屬性為它添加文字內容。 (這裡我們也可以賦予偽元素 content: ''
為空的)所以我們可以任何的給 before 和 after 指定 display 屬性,和不同元素一樣比較自由的。
我們在實作一些組成的時候,也會常常使用這種不污染 DOM 樹,但是能實際創造視覺效果的方式來為頁面添加一些修飾性的內容。
<div> <::before/> content content content content content content content content content content content content content content content content <::after/> </div>
first-letter 和first-line
first-letter
相當於我們有一個元素把內容裡面的第一個字母給括了起來。這個 first-letter
我們是可以任意宣告各種不同的屬性的,但是我們是無法改變它的 content 的。我們應該都看過報紙上的第一個字母會比較大,然後會遊離出來的效果,這個在 CSS 裡面我們就可以用 ::first-letter
的偽元素選擇器了。使用這個來實現相比用 JavaScript 來實現就會更加穩定和程式碼更加優雅一些。
<div> <::first-letter>c</::first-letter>ontent content content content content content content content content content content content content content content content </div>
first-line
是針對排版之後的 line
,其實跟我們原始碼裡面的 first line
沒有任何的關係的。假如說我們的瀏覽器提供的渲染的寬度不同,first-line
在兩個環境裡面它最終括住的元素數量就不一樣多了。所以我們用這個選擇器的時候需要去根據需求的情況使用,很有可能在我們開發機器上和使用者的機器上渲染出來的效果是不一樣的!
<div> <::first-line>content content content content content</::first-line> content content content content content content content content content content content content </div>
這兩個選擇器其實可用的屬性也是有區別的:
first-line 可用屬性
first-letter 可用屬性
#寫一個match 函數。它接受兩個參數,第一個參數是一個選擇器字串性質,第二個是一個 HTML 元素。這個元素你可以認為它一定會在一棵 DOM 樹裡面。透過選擇器和 DOM 元素來判斷,目前的元素是否能夠匹配到我們的選擇器。 (不能使用任何內建的瀏覽器的函數,僅透過 DOM 的 parent 和 children 這些 API,來判斷一個元素是否能夠跟一個選擇器相符。)以下是一個呼叫的範例。更多程式相關知識,請造訪:<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Match Example —— by 三钻</title> </head> <body> <div> <b> <div class="class classA" id="id">content</div> </b> </div> </body> <script language="javascript"> /** * 匹配选择器 */ function matchSelectors(selector, element) { // 先匹配当前元素是否匹配 let tagSelector = selector.match(/^[\w]+/gm); let idSelectors = selector.match(/(?<=#)([\w\d\-\_]+)/gm); let classSelectors = selector.match(/(?<=\.)([\w\d\-\_]+)/gm); /** * 实现复合选择器,实现支持空格的 Class 选择器 * -------------------------------- */ // 检查 tag name 是否匹配 if (tagSelector !== null) { if (element.tagName.toLowerCase() !== tagSelector[0]) return false; } // 检测 id 是否匹配 if (idSelectors !== null) { let attr = element.attributes['id'].value; if (attr) { for (let selector of idSelectors) { if (attr.split(' ').indexOf(selector) === -1) return false; } } } // 检测 class 是否匹配 if (classSelectors !== null) { let attr = element.attributes['class'].value; if (attr) { for (let selector of classSelectors) { if (attr.split(' ').indexOf(selector) === -1) return false; } } } return true; } /** * 匹配元素 */ function match(selector, element) { if (!selector || !element.attributes) return false; let selectors = selector.split(' ').reverse(); if (!matchSelectors(selectors[0], element)) return false; let curElement = element; let matched = 1; // 递归寻找父级元素匹配 while (curElement.parentElement !== null && matched < selectors.length) { curElement = curElement.parentElement; if (matchSelectors(selectors[matched], curElement)) matched++; } // 所有选择器匹配上为 匹配成功,否则是失败 if (matched !== selectors.length) return false; return true; } let matchResult = match('div #id.class', document.getElementById('id')); console.log('Match example by 三钻'); console.log('matchResult', matchResult); </script> </html>
程式設計影片! !
以上是深入了解CSS中的選擇器的詳細內容。更多資訊請關注PHP中文網其他相關文章!