智能選擇器和語義化的CSS
2015-10-08 來自: 陜西印象信息技術(shù)有限公司 瀏覽次數(shù):3580
“結(jié)構(gòu)***服從于功能,這是不變的法則”,建筑工程師“摩天大樓之父”Louis Sullivan如是說。因為工程師不希望讓無辜的人們被碾壓在巨大的建筑物下,這種大拇指式的規(guī)則是相當有用的。在設(shè)計中你應(yīng)該總是以功能為重,然后讓結(jié)構(gòu)在結(jié)果中呈現(xiàn)。如果你以結(jié)構(gòu)為重,雖然這能夠建造出一棟漂亮的摩天大樓,但代價是埋下了許多相當危險的種子。
這些都是關(guān)于建筑師的,那么對前端架構(gòu)師或者“非真正的架構(gòu)師”來說呢?我們需要遵守這個法則還是忽略它?
隨著面向?qū)ο蟮腃SS(OOCSS)的出現(xiàn),越來越多人趨向于“把呈現(xiàn)的語義從文檔語義中脫離出來”。借助于類(classes)的非特指含義,我們能夠以分離的方式來管理一個文檔和一個文檔的外貌。
在這篇文章當中,我們會探索多種為Web文檔添加樣式的方法,他們能把文檔語義與視覺設(shè)計相結(jié)合在一起。通過“聰明的”選擇器的使用,我們通過這種方法給你講解怎樣去查詢語義化的HTML文檔現(xiàn)有的功能特性,以此作為對那些格式良好的標記的一個獎勵。如果你的代碼是正確的,你***會得到你所期望設(shè)計出來的東西。
我想,如果你和我一樣同時在開發(fā)幾個不同的項目,我希望通過這些方法會簡化你的工作流程和并能更輕松地穿梭在這些項目當中。另外,在最后的部分我們 會講解一個更被動的方法:我們會制作一個包含屬性選擇器的CSS書簽來測試那些寫得很糟糕的HTML文檔并使用偽元素來反饋錯誤。
聰明的選擇器
樣式表的發(fā)明使我們能夠?qū)⒛切┟阑疕TML文檔的代碼從HTML文檔中分離出來。但它對于我們書寫標準的HTML文檔方面所帶來的幫助并不像遙控器 那樣為電視機帶來了更高質(zhì)量的電視節(jié)目。它只不過把工作變得更加簡單。因為我們能夠使用一個選擇器來為多個元素添加樣式。(例如 p 可作用于所有的段落元素),頁面的一致性和可維護性也不像以前那樣那么令人畏懼。
p 選擇器的形式就是智能選擇器的一個代表。因為 p 選擇器在語義分類方面具有先天的基礎(chǔ)。不用開發(fā)者多做額外的事情它已經(jīng)知道怎樣確認一個段落以及何時為他們添加樣式,非常簡單而有效,特別是你想為所見即所得編輯器產(chǎn)生的所有的段落元素添加樣式的時候。
但如果說它是智能的選擇器,那非智能的選擇器又是哪些呢?所有需要開發(fā)者介入并更改文檔從而引起樣式上的變化的選擇器都被稱之為非智能的選擇器。class 便是一個經(jīng)典的非智能選擇器因為它不是作為語義約定的一部分出現(xiàn)的。你可以靈活地命名和組織類,但需要***的思考;除非你在文檔上應(yīng)用它們,否則它們不會自動地執(zhí)行任何事情。
使用非智能選擇器是很消耗開發(fā)時間的,因為我們需要手動地為每一個選擇器添加對應(yīng)的不同的樣式并把類名復(fù)制到每一個需要應(yīng)用此類的元素。如果我們沒有 p 標簽,我們不得不使用非智能選擇器來管理段落,例如在每一個段落元素中使用一個 .paragraph類來指明。這樣做的其中一個缺點是這些樣式表不是可移植的,因為你不能再沒有為HTML文檔的標簽指定相應(yīng)的類名的情況下而應(yīng)用這些樣式到所需要的元素當中。
非智能選擇器有時候還是有必要的,并且我們很少人會完全依賴智能選擇器。然而,一些非智能選擇器可能會因為在文檔的結(jié)構(gòu)和表現(xiàn)之間搭配不當而變成“愚蠢的”選擇器。我將會談?wù)?button這個使用頻率極高的“愚蠢”的選擇器。
不同組合的好處
智能選擇器并不限制于HTML規(guī)范中所提供給我們的基礎(chǔ)元素。想要構(gòu)建復(fù)雜的智能選擇器,你可以遵從上下文和功能屬性的組合來區(qū)分基礎(chǔ)元素。一些元素,例如<a>,提供了大量的功能差異給開發(fā)者考慮和利用。其他的元素,例如<p>元素,任何情景中在明確的功能上沒什么差異,但也會根據(jù)上下文來承擔(dān)輕微不同的角色。
header p {
/* styles for prologic paragraphs */
}
footer p {
/* styles for epilogic paragraphs */
}
像這樣的簡單后代選擇器是非常有用的,因為它們讓我們能夠從視覺上看出一個元素的不同類型而無需從物理上更改底層的文檔。這是整個樣式表發(fā)明的原因:既促進物理上的分離而又無需破壞那些存在于文檔和設(shè)計之間的概念上的相互關(guān)系。
不可避免的,一些OOCSS的粉絲們對這樣的后代選擇器是有點不太認同的,它們更喜歡像下面這個例子那樣來做標記,這是從BEM的“定義”文檔中找到的。
1 <ul class="menu"> 2 <li class="menu__item">…</li> 3 <li class="menu__item">…</li> 4 </ul>
我不會再對后代選擇器作進一步的討論,因為我確定你每天都在使用它們,除非你偏好于上面這種過量的概述。換言之,我們接下來會集中于屬性選擇器和屬性中描述的功能所帶來的差異。
超鏈接屬性
即使是那些CSS和HTML之間的概念分離的擁護者也樂于承認一些屬性——除了類和自定義數(shù)據(jù)屬性以外的大多數(shù)的屬性,實際上和文檔的內(nèi)部工作的有重要關(guān)系的。沒有href屬性你的超鏈接不會鏈接到任何東西。沒有type屬性,瀏覽器無法知道渲染哪一個類型的input元素。沒有title屬性,你的abbr元素則可能代表任何東西。
像這樣的屬性有助于改善文檔細節(jié)的語義化,否則你需要去確認主題元素是否正確被渲染以及運作。如果它們不存在,那么它們應(yīng)該被創(chuàng)造,如果它們已經(jīng)存在了,那為什么不適用它們呢?你不能夠只寫CSS而不寫HTML吧。
REL屬性
rel屬性是鏈接關(guān)系的一個標準屬性,是一個描述鏈接的具體的用途的一個方法。并不是所有的鏈接的功能都是相同的。得益于眾多的WordPress的使用者,rel="prev"和rel="next"成為兩個***廣泛采用的值,并有助于描述分頁博客內(nèi)容的每個單獨頁面之間的關(guān)系。從語義上來說,一個擁有rel屬性的a標簽仍然是一個a標簽,但我們已經(jīng)能更加具體地表現(xiàn)它了。這和類不相同,這種具體是從語義上間接表現(xiàn)的。
rel屬性應(yīng)該只被用在合適的地方,因為它們是被HTML的功能規(guī)范所維護的,并能因此被不同的用戶代理采用從而提高用戶體驗和搜索引擎的精que度。那么,你曾像下面這樣為鏈接添加過樣式嗎?使用簡單的屬性選擇器,如:
[rel="prev"] {
/* styling for "previous links" */
}
[rel="next"] {
/* styling for "next" links */
}
屬性選擇器被所有的瀏覽器所支持除了***古老和落后的瀏覽器(IE6),因此只要這個屬性存在于標簽中的時候沒有任何理由不去使用它們。在它的優(yōu)先級方面,它和類具有相同的權(quán)重值。然而,我記得它被建議,我們應(yīng)該將文檔和表現(xiàn)語義分離。我不想浪費這個rel屬性,所以我***hao元素設(shè)置一個毫無意義的屬性并通過它來進行樣式的添加。
1 <a href="/previous-article-snippet/" rel="prev" class="prev">previous page</a>
此處首先要注意的第1件事情是,上面元素中唯yi沒有對文檔的語義有幫助的是類這個屬性。換句話說,類在上面的文檔中式唯yi的沒有起到什么功能上的作用的東西。在實際中,這意味著類是唯yi的打破了分離定律的并被解釋為:它實際存在于文檔中卻又沒對文檔的結(jié)構(gòu)有任何幫助。
好了,說了這么多抽象的概念,但它的可維護性方面如何?大家都接受使用class作為樣式鉤子,讓我們看看當通過編輯或重構(gòu)時移除了一些屬性的時候會發(fā)生什么事情。假設(shè)我們使用了偽元素來為[rel="prev"]鏈接的文字前面添加一個左指箭頭:
1 .prev:before {2 content: '\2190'; /* encoding for a left-pointing arrow ("←") */3 }
移除類的同時也會同時移除了偽元素,反過來說這個舉動會將箭頭所移除。但沒有了這個箭頭,將沒有其他的東西能告訴我們鏈接現(xiàn)存的prev關(guān)系。出于同樣的原因,移除rel屬性也會導(dǎo)致箭頭的不完整:因為雖然class會繼續(xù)讓這個箭頭得以顯示,卻總是使文檔的狀態(tài)關(guān)系丟失。只有直接的通過語義的屬性來給出樣式并應(yīng)用,你才能讓你的代碼和你自己保持真實。只要是文檔中存在的真實的功能,你就要讓它被看見。
屬性字串
我可以想象你正在想什么:“這很有趣,但在超鏈接中有多少個這樣的語義樣式鉤子?我還是會不得不依賴類來應(yīng)用樣式?!蹦敲凑埬憧纯聪旅娌煌逆溄庸δ艿牟煌耆牧斜戆桑卸际且詀元素為基礎(chǔ)的:
links to external resources,
links to secure pages,
links to author pages,
links to help pages,
links to previous pages (see example above),
links to next pages (see example above again),
links to PDF resources,
links to documents,
links to ZIP folders,
links to executables,
links to internal page fragments,
links that are really buttons (more on these later),
links that are really buttons and are toggle-able,
links that open mail clients,
links that cue up telephone numbers on smartphones,
links to the source view of pages,
links that open new tabs and windows,
links to JavaScript and JSON files,
links to RSS feeds and XML files.
這就是鏈接功能上的差異,所有的類型都可以被用戶代理所識別?,F(xiàn)在讓我們思考一個問題,為了讓所有的這些具體鏈接類型都執(zhí)行不同的功能,它們***要 有不同的屬性。也就是說,為了執(zhí)行不同的功能,鏈接的書寫形式是需要有所變化的;并且,如果它們書寫形式不同,它們就可以通過這個來添加不同的樣式。
在準備這篇文章的時候,我對一個構(gòu)思作了一個檢驗,命名為Auticons。Auticons是一個圖標字體和樣式表,用于自動地為鏈接添加樣式。里面的所有選擇器都是屬性選擇器而沒有一個類,為格式良好的超鏈接來調(diào)用樣式。
在許多的案例中,Auticons 對href值的一個子集進行了查詢來確定超鏈接的功能。通過的它們的屬性值的開頭或結(jié)尾來對相應(yīng)的元素添加樣式,又或者是根據(jù)它們屬性值是否包含了一個指定的字串來查找對應(yīng)元素也是可能的。下面是一些常普通的例子。
安全協(xié)議 每一個格式良好的URL(例如:絕dui地址的URL)會以一個URI scheme 加一個冒號開始。在網(wǎng)絡(luò)中我們***常見的就是http:,但是mailTo:(用于簡單郵件傳輸協(xié)議SMTP)和tel:(用于電話號碼)也是很常用的。如果我們可以知道超鏈接的href的值會如何開始,我們可以利用這個約定來作為樣式鉤子。在下面的用于安全頁面的例子,我們使用^=比較器,意思為“以...開始”。
1 [href^="https:"] {2 /* style properties exclusive to secure pages */3 }
在Auticons 中,根據(jù)一個特定的用于識別href屬性的語義模式,連到安全頁面的鏈接用一把鎖來做裝飾。這樣做的優(yōu)點是:
安全頁面的鏈接 —— 也僅僅是安全頁面 —— 能夠通過一把掛鎖來作使它像是一個安全頁面鏈接。
那些不再鏈接到安全頁面的鏈接會失去http協(xié)議同時用作比喻的掛鎖也會消失。
新的安全頁面的鏈接也會自動采用這個掛鎖來標志這個鏈接。
當應(yīng)用于動態(tài)內(nèi)容的時候,這個選擇器顯得相當?shù)刂悄?。因為安全鏈接具有它特定的URI scheme,所以屬性選擇器能夠預(yù)料到它的調(diào)用:一旦編輯器鍵入一些包含安全鏈接的內(nèi)容,鏈接便會自動應(yīng)用樣式來給予用戶一個它是一個安全鏈接的感覺。 因為不需要什么類以及對HTML進行編輯,所以簡單的Markdown 中的鏈接是這樣的:
[Link to secure page](https://payment.example.com/)
但要注意的是使用 [href^="https:"]也不是***正確的,因為也不是所有的 HTTPS 真正***。不過,這僅僅是當瀏覽器本身不太可靠的情況下。***瀏覽器都會在顯示 HTTPS 的時候在地址欄渲染一個掛鎖。
文件類型
正如我說過的,你也可以通過 href屬性以什么結(jié)尾來為超鏈接添加樣式。在實踐中,這意味著你可以使用CSS來指出鏈接所指向的文件類型。Auticons 支持.txt, .pdf, .doc, .exe和其他的一些格式,下面是.zip格式的一個例子,使用$=來決定href以什么結(jié)尾:
1 [href$=".zip"]:before, 2 [href$=".gz"]:before {3 content: '\E004'; /* unicode for the zip folder icon */4 }
組合
你知道如何在一個元素中添加多個類的方法來建立樣式吧?很好,其實你可以用屬性選擇器來幫你自動完成這些工作。讓我們來對比一下:
/* The CSS for the class approach */
.new-window-icon:after {
content: '[new window icon]';
}
.twitter-icon:before {
content: '[twitter icon]';
}
/* The CSS for the attribute selector approach */
[target="_blank"]:after {
content: '[new window icon]';
}
[href*="twitter.com/"]:before {
content: '[twitter icon]';
}
(注意,*=比較器的意思是“內(nèi)容”,如果href的值包含字串twitter.com/,那么樣式便會應(yīng)該到該元素上)
<!-- The HTML for the class approach -->
<a href="http://twitter.com/heydonworks" target="_blank" class="new-window-icon twitter-icon">@heydonworks</a>
<!-- The HTML for the attribute selector approach -->
<a href="http://twitter.com/heydonworks" target="_blank">@heydonworks</a>
任何負責(zé)添加一個到Twitter 頁面的超鏈接的內(nèi)容編輯器,現(xiàn)在只需要知道兩件事:URL和如何在新標簽中打開。因為屬性選擇器能幫助他們找到對應(yīng)的超鏈接。
繼承
還有一些沒有考慮到的地方是:如果有一個不匹配任何屬性選擇器的鏈接呢?如果這個超鏈接是一個普通的舊超鏈接呢?這個選擇器是一個很容易被記住的并且追求性能極zhi的人會很樂意聽到這種話“已經(jīng)沒有別的比它還要更簡練了”。
層疊的屬性選擇器的繼承性與和類層疊在一起的時候是一樣的。首先,它會對你的a添加樣式 —— 假設(shè)是一個text-decoration: underline規(guī)則來提高鏈接的可訪問性;然后使用你所提供的屬性選擇器來為相應(yīng)的a元素進一步添加層疊的樣式。像IE7 這樣的瀏覽器并不完全支持偽元素。但因為繼承的關(guān)系,鏈接還是會應(yīng)用a選擇器中的樣式。
a {
color: blue;
text-decoration: underline;
}
a[rel="external"]:after {
content: '[icon for external links]';
}
實際的按鈕應(yīng)該是真實的
在下面的部分,我們會詳述我們這個CSS書簽的結(jié)構(gòu)來講解代碼上的錯誤。在做這件事前,首先讓我們來看看可能會有什么糟糕的選擇器潛入我們的工作流程中。
OOCSS的信徒們?nèi)匀粓猿质褂妙愐驗樗鼈兪强芍赜玫?,就像組件一樣。因此,.button比#button***。不過,我能想到***的按鈕樣式的選擇器。它的名字也很容易記住。
The <buttonelement represents a button. – W3C Wiki
Topcoat是一個OOCSS的基于BEM的UI框架,來自Adobe。Topcoat中的各種各樣的按鈕樣式代碼超過了450行,如果把注釋塊也加進去的話。里面的注釋塊建議我們用類似這個例子的方式來應(yīng)用按鈕的樣式。
1 <a class="topcoat-button">Button</a>
這個不是一個button。因為,如果它是一個按鈕,應(yīng)該是使用<button>來做標簽。實際上,在每一個我們已知的瀏覽器中,如果使用<button>來表現(xiàn)一個按鈕而不添加任何樣式,它默認看起來也會像一個按鈕。但上面這個例子不是的,因為它用的是<a>標簽,本應(yīng)該是代表一個超鏈接,實際上,它缺少一個href,這意味著它甚至不算是一個超鏈接。在技術(shù)上,它只是一個占位符,一個沒有完成的不完整的超鏈接。
一只穿著鯊魚服裝的小狗并不是鯊魚
Topcoat的CSS中的示例僅僅是一個范例,但前提是類定義了元素并且HTML不是欺騙性的(你應(yīng)該使用button標簽而不是a標簽來描述按鈕)。再多的“有意義的斷字”的類名添加也不能彌補你這樣丑陋地使用非智能選擇器和犯下的代碼上的錯誤。
更新:因為寫了這篇文章,Topcoat.io 已經(jīng)使用<button>標簽來取代上面那個例子。這簡直太棒了!然而,我對它們使用.is-disabled類來指明這個按鈕是禁用的而忽略了disabled屬性持保留意見。你可以在評論區(qū)看到我和Topcoat 的代表在這方面的討論。對與促進OOCSS 所帶來的WEB標準的災(zāi)難,你可以在 semantic-ui.com中自行發(fā)現(xiàn),它們示例中的“標準按鈕”居然是一個包含空的<i>標簽的<div>標簽。
看見是么就是什么
“如果它看起來像只鴨,游泳時也像只鴨,叫聲也像只鴨,那么它可能是一只鴨。”
一個裝飾得像按鈕一樣的鏈接,并且還能觸發(fā)像點擊按鈕那樣的Javascript 事件,對很多人來說,它是一個按鈕。然而,這只意味著它已經(jīng)通過了前兩個階段的“鴨測試”。為了讓所有的用戶能夠運用歸納推理和辨別按鈕,它也***像這 樣。因為它仍然是一個鏈接,盡量避免這種不必要的混淆,因為這種輔助技術(shù)的使用者沒有追求一個語義但這又恰好是我們應(yīng)當承擔(dān)的職責(zé)。
盡管如此,有些人還是堅持用a作為按鈕的標簽。超鏈接是比較容易完全重新地樣式化的一個元素。如果選擇a標簽作為元素,那么只有一個辦法讓它接近真正的按鈕。你猜對了:你***使用WAI ARIA role 屬性值來指明它的含義??梢詢H僅通過下列的屬性選擇器來確認一個超鏈接因為某些原因看起來像是一個按鈕。
1 [role="button"] {2 /* semantic CSS for modified elements that are announced as “button” in assistive technologies */3 }
質(zhì)量保證的屬性選擇器
“CSS給予了class 屬性強大的力量,作者可以基于那些沒有任何表現(xiàn)的元素(例如DIV和SPAN)在想象中設(shè)計他們的“文檔語言”,并通過class 屬性來賦予他們樣式信息。作者應(yīng)該避免這種行為即使這種文檔語言的結(jié)構(gòu)元素經(jīng)常得到大家認可和接受它們的意義?!?– “選擇器,” CSS Level 2, W3C
我們具有a和 button兩個元素的原因是為了在語義上劃分兩種完全不同的功能交互。超鏈接代表著你將會去到的某個位置的符號,而按鈕是作為事件或者動作的觸發(fā)源。一個是關(guān)于位置移動的,另一個則是側(cè)重于轉(zhuǎn)換的。一個是促進脫離的,另一個是促進結(jié)合的。
為了確保我們不會做什么愚蠢的事情來讓我們的按鈕和超鏈接產(chǎn)生混淆。我們會創(chuàng)建一個使用智能屬性選擇器的CSS書簽并來測試兩個元素各自的有效性和質(zhì)量。
受到Eric Meyer的文章的一點啟發(fā)以及從DiagnostiCSS 中獲取了一些線索,這個樣式表會結(jié)合屬性選擇器和:not選擇器(或者非偽類) 來在HTML 中把問題高亮出來。與其他的兩種實現(xiàn)不同,它會使用偽元素來將錯誤打印到屏幕上。每一個錯誤都會采用漫畫字體以及粉紅色的背景來顯示。
通過把功能和表單連接起來,我們可以看到丑陋的HTML 會間接地導(dǎo)致丑陋的 CSS。這是設(shè)計師濫用文檔帶來的后果。嘗試一下把revenge.css保存到你的CSS書簽中,并單擊這個書簽以在任何你喜歡的頁面觸發(fā)它。注意:它不會在服務(wù)在https協(xié)議上的頁面中起作用。
REVENGE.CSS
Drag to your bookmarks bar.
規(guī)則1
如果它是個超鏈接,那它就應(yīng)該有href屬性。
a:not([href]):after {
content: 'Do you mean for this to be a link or a button, because it does not link to anything!';
display: block !important;
background: pink !important;
padding: 0.5em !important;
font-family: 'comic sans ms', cursive !important;
color: #000 !important;
font-size: 16px !important;
}
注意:在這個例子中我并不是要測試屬性的值,而是測試這個屬性是否有被設(shè)置,也就是說,[href]匹配的是任何含有href屬性的元素。這個測試 僅僅適用于超鏈接。這個測試可以這樣來理解,“對于每一個不具有href屬性的元素,為