无法获取未定义或 null 引用的属性“text”_【CSS】是时候开始用 CSS 自定义属性了...
自定義屬性(有時候也被稱作CSS變量或者級聯變量)是由CSS作者定義的,它包含的值可以在整個文檔中重復使用。由自定義屬性標記設定值(比如:--main-color: black;),由var() 函數來獲取值(比如:color: var(--main-color);)
復雜的網站都會有大量的CSS代碼,通常也會有許多重復的值。舉個例子,同樣一個顏色值可能在成千上百個地方被使用到,如果這個值發生了變化,需要全局搜索并且一個一個替換(很麻煩哎~)。自定義屬性在某個地方存儲一個值,然后在其他許多地方引用它。另一個好處是語義化的標識。比如,--main-text-color 會比 #00ff00 更易理解,尤其是這個顏色值在其他上下文中也被使用到。
自定義屬性受級聯的約束,并從其父級繼承其值。
目前,css 的預處理幾乎已經成為?web?(前端)開發的標準。css 預處理的好處之一就是可以使用變量。這樣很大程度上避免了?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);
/* Valid CSS custom properties can be reused later in, say, JavaScript. */
--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;
/* 10px is used because --box-margin is not defined. */
margin: var(--box-margin, 10px);
}
當然,你可以復用另一個變量來聲明一個新的變量:
.box{/* The --main-padding variable is used if --box-padding is not defined. */
padding: var(--box-padding, var(--main-padding));
--box-text: 'This is my box';
/* Equal to --box-highlight-text:'This is my box with highlight'; */
--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; /* DOESN'T work */
padding: calc(var(--spacer)*1px) 0; /* WORKS */
}
作用域和繼承
在說 css 自定義屬性(變量)的作用域前,讓我們先回顧一下 js 預處理的作用域,這有利于我人更好的理解和比較。
我們知道,js?var?的變量的作用域被限制在它所在的 function 中。
let?和?const?性質也是類似的,不過它們都是塊級變量。
js 中的閉包可以對外暴露一個 function 的變量/屬性 — 作用域鏈。js 閉包有三個作用域鏈:
自己內部的作用域變量
外部方法的變量
全局變量
預處理器在這類情況大多是一致的,在這里用 sass 舉例,是因為它應該是目前最受歡迎的 css 預處理器。
sass 中,有兩類變量:local and global。
一個全局變量可以被聲明在任意選擇器區塊的外面,否則,這個變量就是本地的。
任何一個嵌套的代碼塊都可以訪問閉包作用域內的變量(同 javascript);
一個全局的變量可以被定義在選擇器塊作用域的
這意味著,在 sass 中,變量的作用域很大程度上依賴于代碼的上下文結構。
但 css 自定義屬性默認是繼承的,和 css 一樣,也是級聯的。
你不需要在一個選擇器外用全局變量聲明一個自定義屬性,這不是有效的 css,css 自定義屬性的全局作用域實際上是?:root,因此這個屬性是全局可用的。
用我們現有的語法知識,將一個 sass 的例子適用于 html 和 css。搞一個使用原生 css 自定義屬性的例子:
html:
globalclass="enclosing">
enclosing class="closure">
closure
css:
:root {--globalVar: 10px;
}
.enclosing {
--enclosingVar: 20px;
}
.enclosing .closure {
--closureVar: 30px;
font-size: calc(var(--closureVar) + var(--enclosingVar) + var(--globalVar));
/* 60px for now */
}
呈現結果:
http://codepen.io/malyw/pen/MJmebz
預處理器不會知道 dom 的結構
假設我們想使用?default?的 font-size,如果有?highlighted?類,就用它的默認字體來突出顯示。
html:
class="default">defaultclass="default highlighted">default highlighted
css:
.highlighted {--highlighted-size: 30px;
}
.default {
--default-size: 10px;
/* Use default-size, except when highlighted-size is provided. */
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;
/* Use default-size, except when highlighted-size is provided. */
@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; /* reset all CSS custom properties */
all: initial; /* reset all other CSS styles */
}
使用例子
這里有一些 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;
/* Instead of: box-shadow: 0 0 30px 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 屬性:
/*** Gives a CSS custom property value applied at the element
* element {Element}
* varName {String} without '--'
*
* For example:
* readCssVar(document.querySelector('.box'), 'color');
*/
function readCssVar(element, varName){
const elementStyles = getComputedStyle(element);
return elementStyles.getPropertyValue(`--${varName}`).trim();
}
/**
* Writes a CSS custom property value at the element
* element {Element}
* varName {String} without '--'
*
* For example:
* readCssVar(document.querySelector('.box'), 'color', 'white');
*/
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)。
方法如下:
// EventsonMouseMove(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 自定義屬性的一個簡單方法之一就是在?CSS generated content?中直接顯示其內容(比如一般情況下都是字符串),這樣的話,瀏覽器就會自動顯示出當前應用的值。
body:after {content: '--screen-category : 'var(--screen-category);
}
瀏覽器測試情況
從目前來看, css 自定義協議可以支持多數的主流瀏覽器:
你可以直接原生使用了。
如果你需要支持老的瀏覽器,可以學習一下它的語法和示例,用切換 css 或使用相關預處理器的方法來使用它。
當然,我們需要檢測一下 css 和 js 中的支持,以便提供降級和增強的功能。
這個對 css 來說其實相當簡單,我們可以用?@supports?條件語法來判斷:
@supports ( (--a: 0)) {/* supported */
}
@supports ( not (--a: 0)) {
/* not supported */
}
在 js 中,你可以用?CSS.supports()?方法來檢測此的特征:
const isSupported = window.CSS &&window.CSS.supports && window.CSS.supports('--a', 0);
if (isSupported) {
/* supported */
} else {
/* not supported */
}
如你所見, css 自定義屬性不是支持每一個瀏覽器的。我們可以漸進式的在支持這些特性的瀏覽器中使用它來增強你的應用。
例如:你制作兩個 css 文件,一個用 css 自定義屬性,一個不用,在這種方法中,屬性是內聯方式,我們將在下來的內容中討論它。
<link href="without-css-custom-properties.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 自定義屬性很好的時間,為以后瀏覽器原生支持做好準備。
點這,與大家一起分享本文吧~總結
以上是生活随笔為你收集整理的无法获取未定义或 null 引用的属性“text”_【CSS】是时候开始用 CSS 自定义属性了...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: mybatis依赖_Spring Boo
- 下一篇: python执行shell命令、并获取执