深入解析CSS中的自定義屬性

CSS 預(yù)處理器是一個(gè)非常出色的工具,但是它們的變量是靜態(tài)的,有語(yǔ)法作用域。Native CSS 變量,從另一面來(lái)看,它們是一個(gè)完全不同類(lèi)型的變量:因?yàn)樗鼈兪莿?dòng)態(tài)的,他們的作用域是DOM,事實(shí)上,這也是困惑該不該稱(chēng)他們?yōu)樽兞?,它們?shí)際上是CSS 屬性,這也給了他們一個(gè)機(jī)會(huì),來(lái)解決這個(gè)功能完全不同的問(wèn)題。
在這篇文章中,我將討論一些CSS 自定義屬性這個(gè)功能,而且不用CSS 預(yù)處理器來(lái)做。當(dāng)然我還演示一些新的設(shè)計(jì)模式,自定義功能的啟用。文章最后討論一下,我認(rèn)為在未來(lái)最有可能的是預(yù)處理變量和自定義變量一起使用,兩個(gè)東西取長(zhǎng)補(bǔ)短,珠聯(lián)璧合。
預(yù)處理器變量的限制
在繼續(xù)寫(xiě)之前,我想強(qiáng)調(diào)的是,我真的很喜歡CSS 預(yù)處理器,我的所有項(xiàng)目都在使用它。預(yù)處理器做了一件非常了不起的事情,即時(shí)你知道他最終出來(lái)的就是原始的CSS,任然可以感受這個(gè)神器的時(shí)代。
任何工具,都有他的局限性,有一個(gè)炫酷的外觀會(huì)讓人驚喜而忽略了其中的限制,特別是新用戶(hù)。
Preprocessor variables aren’t live
也許受預(yù)處理限制,在媒體查詢(xún)中,最常見(jiàn)的新手也無(wú)力吐槽定義變量或使用@extend
- $gutter: 1em;
- @media (min-width: 30em) {
- $gutter: 2em;
- }
- .Container {
- padding: $gutter;
- }
如果你編譯上面的代碼,你得到是:
- .Container {
- padding: 1em;
- }
如你所見(jiàn),媒體查詢(xún)被廢棄,變量賦值被忽略。
從理論上講,雖然sass 負(fù)責(zé)申明條件變量,但這樣做也是一個(gè)挑戰(zhàn),枚舉所有Permutations—exponentially 會(huì)增加CSS的最終大小。
預(yù)處理器變量不能級(jí)聯(lián)(層疊)
每當(dāng)你使用變量,作用域的范圍不可避免,這個(gè)變量應(yīng)該全局嗎?應(yīng)該是file/module?還是塊作用域?
CSS 最終是為HTML的樣式,事實(shí)證明還有另外一種有用的方法是變量的范圍:DOM 元素,但是preprocessors不能運(yùn)行在瀏覽器且從未看見(jiàn)標(biāo)記。
參考一個(gè)網(wǎng)站,試圖給<html> 的元素添加一個(gè) class user-setting-large-text 他們更傾向于更大的文本大小 。
一旦這個(gè)class設(shè)置,更大$font-size變量賦值就會(huì)運(yùn)用:
- $font-size: 1em;
- .user-setting-large-text {
- $font-size: 1.5em;
- }
- body {
- font-size: $font-size;
- }
但是,就像上面媒體查詢(xún)例子,Sass 直接忽略變量賦值, 意味著這種事是不可能的。他輸出的:
- body {
- font-size: 1em;
- }
預(yù)處理器變量不繼承
雖然繼承是級(jí)聯(lián)的一部分,但是我還是要提一下,因?yàn)楹芏啻挝蚁胧褂眠@個(gè)功能都未能用成。
有一種情況,你有Dom元素在顏色風(fēng)格基礎(chǔ)上有什么的變化時(shí)候,你可以用在他們的父元素上。
- .alert { background-color: lightyellow; }
- .alert.info { background-color: lightblue; }
- .alert.error { background-color: orangered; }
- .alert button {
- border-color: darken(background-color, 25%);
- }
上面的Sass代碼是無(wú)效的,但你應(yīng)該能理解這代碼試圖要完成什么。
它最后試圖使用sass的darken函數(shù)用在background-color屬性,但button元素繼承它的父class元素.alert。如果class info或者error已經(jīng)添加到alert(或者通過(guò)JavaScript設(shè)置背景顏色或用戶(hù)樣式),按鈕元素希望能夠得到這兩個(gè)顏色。
現(xiàn)在這個(gè)雖然不會(huì)在sass 運(yùn)行,因?yàn)轭A(yù)處理器不知道DOM結(jié)構(gòu),但還是希望搞清楚這類(lèi)型的東西可能有哪些用處。
說(shuō)一個(gè)特定的用例:這也是在繼承DOM屬性的可訪問(wèn)性運(yùn)行color 函數(shù)的原因。舉個(gè)栗子,為了確保文本總是可讀,且充分與背景顏色形成鮮明對(duì)比。通過(guò)自定義屬性和新的CSS Color函數(shù),這將很快成為可能!
預(yù)處理器變量不能相互協(xié)作
這是一個(gè)明顯呈下降趨勢(shì)的預(yù)處理器,如果你用PostCSS 建立一個(gè)網(wǎng)站,你想使用第三方組件,不好意思,你只有通過(guò)Sass的themeable。
與第三方分享預(yù)處理器變量在不同的工具集成或第三方托管的CND樣式與都非常困難(至少不容易)。
本地CSS自定義屬性將與任何CSS預(yù)處理或者原CSS正好相反。
自定義屬性有何不同
你可能已經(jīng)猜到了,我上面列出的適用于CSS 自定義屬性沒(méi)有任何限制,但也許更重要的不是說(shuō)他們不適用,而是為什么他們不用。
CSS自定義屬性就像常規(guī)的CSS屬性一樣,他們的操作方式完全相同。
像普通的CSS屬性,自定義屬性是動(dòng)態(tài)的,他們可以在運(yùn)行時(shí)修改,也可以在媒體查詢(xún)時(shí)通過(guò)更改DOM添加一個(gè)新類(lèi),同時(shí)也可以指派內(nèi)聯(lián)元素和一個(gè)常規(guī)CSS里申明選擇器。還可以通過(guò)正常的cascade規(guī)則或者使用JavaScript覆蓋。最主要的是,他們的可以繼承的,所以當(dāng)他們應(yīng)用到DOM元素的時(shí)候,他們的子元素也會(huì)繼承屬性。
為了更簡(jiǎn)潔,預(yù)處理器變量是語(yǔ)法作用域和編譯后靜態(tài)。自定義屬性作用域是DOM,他們都很靈活。
實(shí)際案例
如果你仍然不確定自定義屬性可以做到這一點(diǎn),而預(yù)處理器不行,我這里給一些例子。
不論真假,有大量非常好的例子我都很想展示,但為了不讓這篇文章太丑,我選了兩個(gè)。
我選擇這些例子不僅僅因?yàn)樗鼈兊睦碚?,它們也是我們過(guò)去實(shí)際面臨的挑戰(zhàn),我依然記得試圖用預(yù)處理器,但這是不可能的?,F(xiàn)在好了,直接自定義屬性走起。
媒體查詢(xún)的響應(yīng)式特性
很多網(wǎng)站在項(xiàng)目布局使用“gap”和“gutter” 定義默認(rèn)間距和填充頁(yè)面各個(gè)部分,很多時(shí)候,你想要這個(gè)“gutter”的值根據(jù)瀏覽器窗口的大小而不同。在大屏幕上你想要每一項(xiàng)之間有足夠的空間,但小屏幕又負(fù)擔(dān)不起那么大的空間,所以“gutter”的值要較小。
正如我上面提到的,在媒體查詢(xún)里面Sass 不能正常運(yùn)行,所以你必須每個(gè)單獨(dú)處理。
下面的例子定義了變量$gutterSm, $gutterMd和$gutterLg,然后給每個(gè)變量申明一個(gè)單獨(dú)的規(guī)則:
- /* Declares three gutter values, one for each breakpoint */
- $gutterSm: 1em;
- $gutterMd: 2em;
- $gutterLg: 3em;
- /* Base styles for small screens, using $gutterSm. */
- .Container {
- margin: 0 auto;
- max-width: 60em;
- padding: $gutterSm;
- }
- .Grid {
- display: flex;
- margin: -$gutterSm 0 0 -$gutterSm;
- }
- .Grid-cell {
- flex: 1;
- padding: $gutterSm 0 0 $gutterSm;
- }
- /* Override styles for medium screens, using $gutterMd. */
- @media (min-width: 30em) {
- .Container {
- padding: $gutterMd;
- }
- .Grid {
- margin: -$gutterMd 0 0 -$gutterMd;
- }
- .Grid-cell {
- padding: $gutterMd 0 0 $gutterMd;
- }
- }
- /* Override styles for large screens, using $gutterLg. */
- @media (min-width: 48em) {
- .Container {
- padding: $gutterLg;
- }
- .Grid {
- margin: -$gutterLg 0 0 -$gutterLg;
- }
- .Grid-cell {
- padding: $gutterLg 0 0 $gutterLg;
- }
- }
使用自定義屬性來(lái)完成相同的東西,你只需要定義樣式即可。你可以使用一個(gè) gutter 屬性,然后隨著媒體查詢(xún)的變化,更新gutter 的值,它就會(huì)做出相應(yīng)的變化。
- :root { --gutter: 1.5em; }
- @media (min-width: 30em) {
- :root { --gutter: 2em; }
- }
- @media (min-width: 48em) {
- :root { --gutter: 3em; }
- }
- /*
- * Styles only need to be defined once because
- * the custom property values automatically update.
- */
- .Container {
- margin: 0 auto;
- max-width: 60em;
- padding: var(--gutter);
- }
- .Grid {
- --gutterNegative: calc(-1 * var(--gutter));
- display: flex;
- margin-left: var(--gutterNegative);
- margin-top: var(--gutterNegative);
- }
- .Grid-cell {
- flex: 1;
- margin-left: var(--gutter);
- margin-top: var(--gutter);
- }
雖然有額外增加的自定義屬性語(yǔ)法,但是相比冗長(zhǎng)的代碼完成同樣的事明顯好很多。這里只考慮了三個(gè)變量,如果變量越多,這將節(jié)省更多的代碼。
下面的演示使用的是上面的代碼自動(dòng)構(gòu)建的一個(gè)基本的網(wǎng)站布局,gutter的值跟隨窗口的變化而變化,瀏覽器的支持自定義屬性的話(huà),效果屌屌的!
語(yǔ)境樣式
語(yǔ)境樣式(樣式元素根據(jù)它出現(xiàn)在Dom)在CSS里是一個(gè)有爭(zhēng)議的話(huà)題。 一方面,它是最受人尊敬的CSS開(kāi)發(fā)者警告,另一方面,大多數(shù)人每天都還要用它。
下面例子顯示了大部分人在CSS使用語(yǔ)境樣式方法,使用子代選擇器
- /* Regular button styles. */
- .Button { }
- /* Button styles that are different when inside the header. */
- .Header .Button { }
這種方法有很多問(wèn)題,這種模式一個(gè)代碼味道,它違反了 open/closed 軟件開(kāi)發(fā)原則;修改了一個(gè)封閉組件的實(shí)現(xiàn)細(xì)節(jié)
軟件體 (類(lèi), 模塊, 函數(shù)等) 擴(kuò)展開(kāi)放, 對(duì)修改關(guān)閉。
自定義屬性的改變范圍式定義組件是一個(gè)有趣的方式,用自定義屬性,我們可以在第一次就寫(xiě)一個(gè)實(shí)際上是開(kāi)放擴(kuò)展的組件,這里有一個(gè)例子:
- .Button {
- background: var(--Button-backgroundColor, #eee);
- border: 1px solid var(--Button-borderColor, #333);
- color: var(--Button-color, #333);
- /* ... */
- }
- .Header {
- --Button-backgroundColor: purple;
- --Button-borderColor: transparent;
- --Button-color: white;
- }
這和子選擇器之間的區(qū)別很微妙而且很重要。
當(dāng)使用子選擇器我們宣傳在頁(yè)眉按鈕會(huì)這樣,這樣不同的按鈕如何定義自己,這樣的聲明是獨(dú)裁(借Harry’s 的詞),很難撤銷(xiāo)例外的情況,頁(yè)眉的一個(gè)按鈕不需要這樣的方式。
另外,自定義屬性,按鈕組件仍是沒(méi)有語(yǔ)境且不能完全與header 組件解耦,
按鈕組件簡(jiǎn)單的說(shuō)申明:無(wú)論它們現(xiàn)狀如何,我要自己的風(fēng)格基于這些自定義屬性;
header 組件:我要設(shè)置這些屬性值,由我的子代來(lái)確定和如何使用它們。
主要的區(qū)別是,該擴(kuò)展由按鈕組件選擇,并輕易消除例外情況。
下面的演示說(shuō)明了語(yǔ)境樣式的鏈接和按鈕在網(wǎng)站的標(biāo)題及內(nèi)容區(qū)
創(chuàng)建例外
如果像.promo的組件加到header,然后buttons又加到.promo 里面,使其看起來(lái)像一個(gè)正常按鈕,而不是標(biāo)題按鈕。
如果你用子代選擇器,那你將要給header buttons寫(xiě)一大串樣式,而且還不能影響promo buttons,混亂,容易出錯(cuò),而且容易失控的數(shù)量會(huì)增加:
- /* Regular button styles. */
- .Button { }
- /* Button styles that are different when inside the header. */
- .Header .Button { }
- /* Undo button styles in the header that are also in promo. */
- .Header .Promo .Button { }
- 使用自定義屬性,你可以簡(jiǎn)單的更新任何你想要的新按鈕屬性,或重置他們回默認(rèn)樣式,無(wú)視這些例外,改變的方式總是相同的。
- .Promo {
- --Button-backgroundColor: initial;
- --Button-borderColor: initial;
- --Button-color: initial;
- }
跟React學(xué)
當(dāng)我第一次探索自定義屬性語(yǔ)境樣式的時(shí)候,我很懷疑自己。像前面說(shuō)的,我傾向于喜歡組件自己定義自己的變化,而不是任何屬性都繼承自父元素。
但是有一件事,動(dòng)搖了我在CSS自定義屬性的觀點(diǎn),那就是React的props 的
React的props依然是動(dòng)態(tài)的,DOM-scoped variables,他們繼承,允許組件上下文關(guān)聯(lián),在React,父組件將數(shù)據(jù)傳遞給子組件,然后子組件定義props,他們?cè)敢饨邮芎褪褂盟鼈?。這種建筑模型通常被稱(chēng)為one-way data flow。
盡管自定義組件是全新的未測(cè)試的領(lǐng)域,我認(rèn)為React model 給了成功的信心,一個(gè)復(fù)雜的系統(tǒng)可以建立在屬性繼承——此外,DOM-scoped variables 是一個(gè)非常有用的設(shè)計(jì)模式。
最大限度的減少副作用
CSS 自定義屬性繼承默認(rèn),在某些情況下,這導(dǎo)致組件的樣式可能沒(méi)有達(dá)到他們的預(yù)期。
在文章上一節(jié)中,我提到可以重置單個(gè)屬性,這可以防止未知值被應(yīng)用到元素的子元素:
- .MyComponent {
- --propertyName: initial;
- }
盡管這不是規(guī)范的一部分,——正在討論屬性附[2],這個(gè)可以用來(lái)重置所有自定義屬性,如果你想白名單幾個(gè)屬性,你可以將他們單獨(dú)繼承,其他的正常即可:
- .MyComponent {
- /* Resets all custom properties. */
- --: initial;
- /* Whitelists these individual custom properties */
- --someProperty: inherit;
- --someOtherProperty: inherit;
- }
管理全局names
如果你一直關(guān)注自定義屬性,那你可能已經(jīng)注意到本身帶有components-specific前綴的組件,如--Button-backgroundColor.
與CSS 大多數(shù)名字一樣,自定義屬性是全局,很是有可能將正在使用命名與其他開(kāi)發(fā)團(tuán)隊(duì)的名稱(chēng)產(chǎn)生沖突。
有一個(gè)簡(jiǎn)單的方法可以避免這個(gè)問(wèn)題,就是堅(jiān)持命名約定,我現(xiàn)在團(tuán)隊(duì)就是這么做的。
對(duì)于更復(fù)雜的項(xiàng)目,你可以考慮像CSS模塊 localifies所有全局名稱(chēng),而且他們最近也表示有興趣支持自定義屬性。
結(jié)束語(yǔ)
如果你在閱讀這篇文章之前,不熟悉CSS 自定義屬性,我希望你能給他一個(gè)機(jī)會(huì)。如果你還在懷疑他的必要性,希望我能改變你的想法。
我敢肯定,自定義屬性能給CSS帶來(lái)一系列的強(qiáng)大的功能和面貌,它還有更多的優(yōu)勢(shì)等待我們?nèi)グl(fā)現(xiàn)。
自定義屬性preprocessor 變量是無(wú)可替代的。盡管如此,preprocessor variables 仍然是許多情況下的不二選擇。正因如此,我堅(jiān)信未來(lái)很多網(wǎng)站都會(huì)結(jié)合使用二者。
自定義屬性為動(dòng)態(tài)主題和預(yù)處理器變量靜態(tài)模板。
我不認(rèn)為這是二選一的情況,讓他們相互競(jìng)爭(zhēng),就像對(duì)手一樣傷害每一個(gè)人。
相關(guān)文章
- 本篇文章主要介紹了css自定義屬性和聚光燈效果的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-02
- 最近微軟宣布Edge瀏覽器支持CSS自定義屬性,雖然表面上看起來(lái)并沒(méi)有太多改變,但其實(shí)卻是一個(gè)新的原始值類(lèi)型,允許跨CSS屬性完全級(jí)聯(lián)變量,直接通過(guò)JavaScript進(jìn)行交互,詳2017-03-27
css如何實(shí)現(xiàn)自定義更為美觀的鏈接提示效果
在默認(rèn)狀態(tài)下可以使用a標(biāo)簽的title屬性實(shí)現(xiàn)鏈接提示效果 雖然能夠?qū)崿F(xiàn)鏈接提示效果,但是往往并不能夠滿(mǎn)足實(shí)際需求,可能需要自定義美觀或者更好的相關(guān)搭配的鏈接提示效果2013-02-16- 今天,CSS預(yù)處理器是Web開(kāi)發(fā)的標(biāo)準(zhǔn)。 預(yù)處理器的一個(gè)主要優(yōu)點(diǎn)是它們使您能夠使用變量, 這有助于您避免復(fù)制和粘貼代碼,并簡(jiǎn)化了開(kāi)發(fā)和重構(gòu)。今天通過(guò)本文給大家分享如何將2018-11-14