天天看點

css html自定義屬性,是時候開始用 CSS 自定義屬性了

自定義屬性(有時候也被稱作CSS變量或者級聯變量)是由CSS作者定義的,它包含的值可以在整個文檔中重複使用。由自定義屬性标記設定值(比如: --main-color: black;),由var() 函數來擷取值(比如: color: var(--main-color);)

複雜的網站都會有大量的CSS代碼,通常也會有許多重複的值。舉個例子,同樣一個顔色值可能在成千上百個地方被使用到,如果這個值發生了變化,需要全局搜尋并且一個一個替換(很麻煩哎~)。自定義屬性在某個地方存儲一個值,然後在其他許多地方引用它。另一個好處是語義化的辨別。比如,--main-text-color 會比 #00ff00 更易了解,尤其是這個顔色值在其他上下文中也被使用到。

自定義屬性受級聯的限制,并從其父級繼承其值。ctrl + c / ctrl + v,也簡化了開發和重構。

我們通常用預處理來定義存儲顔色、字型表現、布局細節等,幾乎可以用在任意地方。

但是預處理的變量存在一定的限制:

你不能動态的改變它

它不會顧及 DOM 結構

不能從 javascript 中讀取或更改資料

作為解決這類問題的“銀彈”,社群發明了 css 自定義屬性 這一技術。本質上看,它運作機制像是 css 變量,工作方式就展現在它的名字上: properties。

自定義屬性為 web 開發開辟了一塊新天地。

聲明和使用自定義屬性的文法

通常,使用一個新的預處理程式或架構,都得從它的文法開始學起。

每一個預處理語言都有自己定義變量的方式,通常都由一個保留字元開始,比如 sass 中的 $ 和 less 中的 @。

自定義屬性的 css 也使用同樣的方法: -- 申明變量,當然它有一個好處:學習使用一次後,在各浏覽器中複用它。

你也許會問:『為什麼不用再用的文法?』

There is a reason. 簡單來說,就是提供一種自定義屬性的方法,可以在任何預處理語言中使用它。 這樣,我們提供并使用的自定義屬性,預處理器并不會編譯它們,這些自定義屬性會直接生成 css,而且你可以在原生環境下利用這些自定義的變量。 這些我會在接下來說明。

(關于這個名稱需要解釋一下:因為想法和目的都非常相似,一些自定義屬性也被稱作是 css 變量,雖然它的正确名字應該是 css 自定義屬性,進一步的閱讀此篇文章,你就會明白這個名稱是最恰當的。)

聲明一個變量來代替正常的 css 屬性,如 color 和 padding,僅需要一個 -- 開關的自定義屬性:

.box{

--box-color: #4d4e53;

--box-padding: 0 10px;

}

屬性的值可以是 顔色值、字元串、布局的類型、甚至是一個表達式。

eg:

:root{

--main-color: #4d4e53;

--main-bg: rgb(255, 255, 255);

--logo-border-color: rebeccapurple;

--header-height: 68px;

--content-padding: 10px 20px;

--base-line-height: 1.428571429;

--transition-duration: .35s;

--external-link: "external link";

--margin-top: calc(2vh + 20px);

--foo: if(x > 5) this.width = 10;

}

這個例子中你可以不太明白什麼是 :root,它實際和 html 一緻,隻是有更高的優先級。

自定義屬性的級聯方式與 css 屬性一樣,而且是動态的,這意味着你可以随時更改,并且根據不同的浏覽器做針對性的處理。

要使用一個變量,你需要使用 var(),此 css function 接收一個自定義的屬性:

.box{

--box-color:#4d4e53;

--box-padding: 0 10px;

padding: var(--box-padding);

}

.box div{

color: var(--box-color);

}

聲明和使用

var() 方法也可以設計參數的預設值。當你不确定是否某個自定義變量已經被定義,又想給一個未定義時的值時,你應該會用到這種方法。 非常簡單,給它傳入第二個參數就行。

.box{

--box-color:#4d4e53;

--box-padding: 0 10px;

margin: var(--box-margin, 10px);

}

當然,你可以複用另一個變量來聲明一個新的變量:

.box{

padding: var(--box-padding, var(--main-padding));

--box-text: 'This is my box';

--box-highlight-text: var(--box-text)' with highlight';

}

運算符 +, -, *, /

在預處理語言中,我們都習慣用基本的運算符來進行計算,為此,css 提供了一個 calc() 函數, 這樣在某一個自定義屬性變化後,浏覽器就會重新得到表達式的值。

:root{

--indent-size: 10px;

--indent-xl: calc(2*var(--indent-size));

--indent-l: calc(var(--indent-size) + 2px);

--indent-s: calc(var(--indent-size) - 2px);

--indent-xs: calc(var(--indent-size)/2);

}

注意的是,在此類表達式中使用不帶機關的變量時就會出現問題。

:root{

--spacer: 10;

}

.box{

padding: var(--spacer)px 0;

padding: calc(var(--spacer)*1px) 0;

}

作用域和繼承

在說 css 自定義屬性(變量)的作用域前,讓我們先回顧一下 js 預處理的作用域,這有利于我人更好的了解和比較。

我們知道,js var 的變量的作用域被限制在它所在的 function 中。

let 和 const 性質也是類似的,不過它們都是塊級變量。

js 中的閉包可以對外暴露一個 function 的變量/屬性 — 作用域鍊。js 閉包有三個作用域鍊:

自己内部的作用域變量

外部方法的變量

全局變量

css html自定義屬性,是時候開始用 CSS 自定義屬性了

預處理器在這類情況大多是一緻的,在這裡用 sass 舉例,是因為它應該是目前最受歡迎的 css 預處理器。

sass 中,有兩類變量:local and global。

一個全局變量可以被聲明在任意選擇器區塊的外面,否則,這個變量就是本地的。

任何一個嵌套的代碼塊都可以通路閉包作用域内的變量(同 javascript);

css html自定義屬性,是時候開始用 CSS 自定義屬性了

一個全局的變量可以被定義在選擇器塊作用域的

這意味着,在 sass 中,變量的作用域很大程度上依賴于代碼的上下文結構。

但 css 自定義屬性預設是繼承的,和 css 一樣,也是級聯的。

你不需要在一個選擇器外用全局變量聲明一個自定義屬性,這不是有效的 css,css 自定義屬性的全局作用域實際上是 :root,是以這個屬性是全局可用的。

用我們現有的文法知識,将一個 sass 的例子适用于 html 和 css。搞一個使用原生 css 自定義屬性的例子:

html:

global

enclosing

closure

css:

:root {

--globalVar: 10px;

}

.enclosing {

--enclosingVar: 20px;

}

.enclosing .closure {

--closureVar: 30px;

font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));

}

呈現結果:

http://codepen.io/malyw/pen/MJmebz

預處理器不會知道 dom 的結構

假設我們想使用 default 的 font-size,如果有 highlighted 類,就用它的預設字型來突出顯示。

html:

default

default highlighted

css:

.highlighted {

--highlighted-size: 30px;

}

.default {

--default-size: 10px;

font-size: var(--highlighted-size, var(--default-size));

}

因為第二段 html 的 default 攜帶着 highlighted,其中的 highlighted properties 就可以通過 var 表達式應用在 element 中。

在這個例子中,--highlighted-size: 30px; 是生效的,這樣反過來可以将設定好的某一個值(如: --highlighted-size)應用在 font-size 上。

這些都是那麼的直接了當:

http://codepen.io/malyw/pen/ggWMvG

.highlighted {

$highlighted-size: 30px;

}

.default {

$default-size: 10px;

@if variable-exists(highlighted-size) {

font-size: $highlighted-size;

}

@else {

font-size: $default-size;

}

}

再來看 sass 的這個例子:

http://codepen.io/malyw/pen/PWmzQO

出現這種情況,是因為所有的 Sass 計算和處理都在編譯時發生,當然,它完全不依賴于代碼的結構,也不了解DOM的結構。

這樣看來,“自定義屬性” 有一個更進階的變量作用域,給通常的 css 級聯屬性增加了一種情況,它會自行識别 dom 的結構并遵循 css 應用的規則。

css 自定義屬性可以識别 dom 結構,并且是動态的

CSS-WIDE 關鍵字和 all 屬性

css 自定義屬性遵循和傳統的 css 屬性一樣的規則。這意味着你可以給它定義任意正常的 css 屬性關鍵字:

inherit 繼承其父元素某一屬性值的關鍵字

initial 應用某一屬性的初始值,(可能是一空值、或是其它 css 屬性預設的值)

unset 當一個屬性預設是繼承父元素的屬性值時,它使用繼承的值;如是屬性不繼承的話,就使用其預設的值

revert 它可以将一屬性值重置為使用者 stylesheet 樣式表中的值,(在 css 自定義屬性中一般是空值)

eg:

.common-values{

--border: inherit;

--bgcolor: initial;

--padding: unset;

--animation: revert;

}

我們再設想一種情況,你想要做一個 css 元件,來确認一下某一進制素有沒有其它的屬性、或是是否無意中将一些自定義屬性應用到上面了。(這種情況下,通常會使用一個子產品化的解決方案)

現在我們有了另一種方法:使用 all CSS property。這是将全部屬性都 reset 的一種簡寫。

這樣,我們可以這樣寫了:

.my-wonderful-clean-component{

all: initial;

}

這樣可以将 component 的全部樣式進行重置。

不幸的是,all 關鍵字不能重置自定義屬性,是否需要加一個字首 -- 來重置所有的正常 css 屬性 — 這個讨論還在進行中。

如此的話,在将來,我們可以這樣重置“所有”屬性:

.my-wonderful-clean-component{

--: initial;

all: initial;

}

使用例子

這裡有一些 css 自定義屬性的使用例子,給大家展示一下它有意思的地方。

模拟一個不存在的 css rules

這些 css 變量的名稱是“自定義屬性”,那麼為什麼不使用它們模拟不存在的屬性呢?

這類屬性有很多:translateX/Y/Z,background-repeat-x/y,box-shadow-color…

我們試着來實作最後一個,在我們的例子中,要改變 hover 狀态下的 box-shadow 的顔色。 需要用純粹的 css rule 來控制,是以我們不建議在 :hover 選擇器中完全複寫 box-shadow。 使用正常的 css 屬性,如下:

.test {

--box-shadow-color: yellow;

box-shadow: 0 0 30px var(--box-shadow-color);

}

.test:hover {

--box-shadow-color: orange;

}

http://codepen.io/malyw/pen/KzZXRq

顔色主題

css 自定義屬性中一個很大衆的用例就是給一個應用設定顔色主題。感覺 css 自定義屬性設計的初衷就是來解決這類問題的。 這裡提供一個很簡單的顔色主題元件。

看這裡code for our button component;

.btn {

background-image: linear-gradient(to bottom, #3498db, #2980b9);

text-shadow: 1px 1px 3px #777;

box-shadow: 0px 1px 3px #777;

border-radius: 28px;

color: #ffffff;

padding: 10px 20px 10px 20px;

}

假設我們要反轉顔色主題。

第一步是将所有顔色變量,擴充成CSS自定義屬性并重寫我們的元件。結果會是一樣的:

.btn {

--shadow-color: #777;

--gradient-from-color: #3498db;

--gradient-to-color: #2980b9;

--color: #ffffff;

background-image: linear-gradient(

to bottom,

var(--gradient-from-color),

var(--gradient-to-color)

);

text-shadow: 1px 1px 3px var(--shadow-color);

box-shadow: 0px 1px 3px var(--shadow-color);

border-radius: 28px;

color: var(--color);

padding: 10px 20px 10px 20px;

}

這裡有我們需要的一切内容。在需要的時候 override 顔色變量來反轉顔色。 我們可以這樣,舉個栗子,給 body 加一個 inverted 類,來改變所應用的顔色變量。

body.inverted .btn{

--shadow-color: #888888;

--gradient-from-color: #CB6724;

--gradient-to-color: #D67F46;

--color: #000000;

}

如下的例子中,通過點選 button 來切換 body 的 inverted 類。

http://codepen.io/malyw/pen/dNWpRd

普通的 css 預處理器不可能在不用“重複” override 代碼的前提下做到這種情況。 一般隻能通過覆寫已有的 css 屬性 rules 的方法,新添加一個 css 規則來實作它。

用了 css 自定義屬性,解決方案就非常優雅了,複制/粘貼代碼情況也會避免,僅需要重新定義變量的值。

結合 javascript 使用 css 自定義屬性

以前,我們想要從 css 向 javascript 傳輸資料,我們經常需要使用一些技巧。通過輸出 css 形式的 json 值來編寫 css 屬性,然後從 javascript 中讀取它們。

現在,我們可以輕松地使用 JavaScript 中的 CSS 變量進行互動,使用大家熟悉的 .getPropertyValue() 和 .setProperty() 方法讀取和寫入它們,這些方法都用于正常 CSS 屬性:

function readCssVar(element, varName){

const elementStyles = getComputedStyle(element);

return elementStyles.getPropertyValue(`--${varName}`).trim();

}

function writeCssVar(element, varName, value){

return element.style.setProperty(`--${varName}`, value);

}

假設我們有一個媒體查詢的值如下:

.breakpoints-data {

--phone: 480px;

--tablet: 800px;

}

因為我們隻是想在 js 中複用它,比如在 Window.matchMedia() 方法中 — 我們可以很輕松的獲得它的值:

const breakpointsData = document.querySelector('.breakpoints-data');

// GET

const phoneBreakpoint = getComputedStyle(breakpointsData)

.getPropertyValue('--phone');

為了展示如何在 js 中調用一個 css 自定義值。在這裡,我建立了一個 3d css 的正方形來響應使用者的操作。

這個并不是非常困難,我們隻需要加一個簡單的背景,再通過調節 translateZ(), translateY(), rotateX() and rotateY() 這幾個 transform 屬性來放置剩下的 5 個面,就可以搞定了。

為了提供正确的透視點,我們将以下内空放在一個 wrapper 中。

剩下的就隻是互動性了,将滑鼠移動時,demo 會變換 X 和 Y 軸的角度 (--rotateX and --rotateY),當滑鼠滾動時,會對圖形進行放大和縮小 (--translateZ)。

方法如下:

// Events

onMouseMove(e) {

this.worldXAngle = (.5 - (e.clientY / window.innerHeight)) * 180;

this.worldYAngle = -(.5 - (e.clientX / window.innerWidth)) * 180;

this.updateView();

};

onMouseWheel(e) {

this.worldZ += delta * 5;

this.updateView();

};

// JavaScript -> CSS

updateView() {

this.worldEl.style.setProperty('--translateZ', this.worldZ);

this.worldEl.style.setProperty('--rotateX', this.worldXAngle);

this.worldEl.style.setProperty('--rotateY', this.worldYAngle);

};

使用者移動滑鼠時,demo 會改變視角,也可以通過滑鼠滑輪來進行放大和縮小。

demo

實質上,我們隻是改變了 css 自定義屬性的值,其它的 (the rotating and zooming in and out) 則是通過 css 完成的。

提示:調試 css 自定義屬性的一個簡單方法之一就是在

body:after {

content: '--screen-category : 'var(--screen-category);

}

浏覽器測試情況

從目前來看, css 自定義協定可以支援多數的主流浏覽器:

css html自定義屬性,是時候開始用 CSS 自定義屬性了

你可以直接原生使用了。

如果你需要支援老的浏覽器,可以學習一下它的文法和示例,用切換 css 或使用相關預處理器的方法來使用它。

當然,我們需要檢測一下 css 和 js 中的支援,以便提供降級和增強的功能。

這個對 css 來說其實相當簡單,我們可以用 @supports 條件文法來判斷:

@supports ( (--a: 0)) {

}

@supports ( not (--a: 0)) {

}

在 js 中,你可以用 CSS.supports() 方法來檢測此的特征:

const isSupported = window.CSS &&

window.CSS.supports && window.CSS.supports('--a', 0);

if (isSupported) {

} else {

}

如你所見, css 自定義屬性不是支援每一個浏覽器的。我們可以漸進式的在支援這些特性的浏覽器中使用它來增強你的應用。

例如:你制作兩個 css 檔案,一個用 css 自定義屬性,一個不用,在這種方法中,屬性是内聯方式,我們将在下來的内容中讨論它。

rel="stylesheet" type="text/css" media="all" />

// JavaScript

if(isSupported){

removeCss('without-css-custom-properties.css');

loadCss('css-custom-properties.css');

// + conditionally apply some application enhancements

// using the custom properties

}

這隻是個示例,下面會介紹一個更優的方法。

如何使用它們

在最近的調查中, sass 依舊是開發社群中首選的 css 預處理器。

是以,我們設計一種方法,在 sass 中使用 css 的自定義屬性。

1. 手動檢查支援情況

手動檢查代碼中是否支援自定義屬性的方法,優點是它可以立即生效(不要忘了我們已經切換到Sass)

$color: red;

:root {

--color: red;

}

.box {

@supports ( (--a: 0)) {

color: var(--color);

}

@supports ( not (--a: 0)) {

color: $color;

}

}

這種方法也存在許多的坑:代碼會變得非常複雜,複制、粘貼的代碼會造成不易維護。

2. 使用一個插件來自動生成 css

PostCSS 現在已經給我們提供了許多的插件,這此插件中有幾個都會在過程中處理 css 自定義屬性(内聯的),正确輸出使它們工作。 假設你僅提供全局變量(例如:你隻是在 :root 選擇符中聲明或改變了 css 自定義屬性),這樣它們的值可以被輕松内聯。

例子:postcss-custom-properties

插件的方法有幾個優點:它支援相關文法,而且支援所有 PostCSS 的基礎功能,而且不用太多的配置。

也存在幾個缺點:插件需要你使用 css 自定義屬性,是以你也沒有準備另一個路徑來切換 sass 變量。 你也不法對變換進行完全的控制,因為這些隻能是在編譯成 css 之後。而且插件也無法提供足夠的調試資訊。

3. css-vars 混合器

在我之前的大量項目中嘗試了許多的 css 樣式政策後,我開始使用 css 自定義屬性。

從 sass 轉到 postcss(cssnext)

從 sass 變量徹底轉到 css 自定義屬性變量

在 sass 中使用 css 變量,檢測它是否被支援

從以上經驗中,我得到了一個基本滿足我需要的解決方案:

開始使用 sass 應該容易上手

用來起沒那麼多步驟,文法也盡可能的接近原生的文法

将 css 的輸出從 inline 模式切換到 css 變量應該非常容易

熟悉 css 自定義屬性的團隊可以直接使用

對于所用變量的邊界值問題應該有調試方法

對此,我建立了 css-vars,一個 sass minin,你可以去這裡找到。 有了它,就可以直接使用 css 自定義屬性文法了。

使用 css-vars Mixin

聲明變量如下:

$white-color: #fff;

$base-font-size: 10px;

@include css-vars((

--main-color: #000,

--main-bg: $white-color,

--main-font-size: 1.5*$base-font-size,

--padding-top: calc(2vh + 20px)

));

使用變量如下:

body {

color: var(--main-color);

background: var(--main-bg, #f00);

font-size: var(--main-font-size);

padding: var(--padding-top) 0 10px;

}

這個 mixin 給幫助你從一個地方(from sass)來控制所有 css 輸出,并且熟悉其文法。而且,你還可以利用 sass 的文法和變量。

當你項目需求的所有的浏覽器都支援 css 變量時,隻需要加上這個句:

$css-vars-use-native: true;

這樣的話:

一個變量定義了沒有被使用,會報日志

變量被重複定義,會報日志

所用的變量沒有定義,而是傳了一個預設值,會的資訊提示

總結

到這裡,你也應該了解了 css 自定義屬性,包括它們的文法、它的進階特性,一些很好的使用例子,和如何結合 js 去使用它。

你也學會了如何檢測裝置是否支援它,它和一般的 css 預處理器有什麼不同,如何在跨浏覽器支援的情況下使用原生的 css 變量。

現在是一個學習 css 自定義屬性很好的時間,為以後浏覽器原生支援做好準備。