若是當年的 CSS 預處理器變量對於初入前端的我來講是開啓了新世界的大門,那麼 CSS 變量對於我來講無疑就是晴天霹靂。其功能不但能夠優雅的處理以前 js 很差處理或不適合的業務需求。更在創造力無窮的前端開發者手中大放異彩。css
在前端的領域中,標準的實現老是比社區的約定要慢的多,前端框架最喜歡的 $ 被 Sass 變量用掉了。而最經常使用的 @ 也被 Less 用掉了。官方爲了讓 CSS 變量也可以在 Sass 及 Less 中使用,無奈只能妥協的使用 --。html
<style> /* 在 body 選擇器中聲明瞭兩個變量 */ body { --primary-color: red; /* 變量名大小寫敏感,--primary-color 和 --PRIMARY-COLOR 是兩個不一樣變量 */ --PRIMARY-COLOR: initial; } /** 同一個 CSS 變量,能夠在多個選擇器內聲明。優先級高的會替換優先級低的 */ main { --primary-color: blue; } /** 使用 CSS 變量 */ .text-primary { /* var() 函數用於讀取變量。 */ color: var(--primary-color) } <style> <!-- 呈現紅色字體,body 選擇器的顏色 --> <div class="text-primary">red</div> <!-- 呈現藍色字體,main 選擇器定義的顏色 --> <main class="text-primary">blue</main> <!-- 呈現紫色字體,當前內聯樣式表的定義 --> <div style='--primary-color: purple" class="text-primary">purple</main>
這裏咱們能夠看到針對同一個 CSS 變量,能夠在多個選擇器內聲明。讀取的時候,優先級最高的聲明生效。這與 CSS 的"層疊"(cascade)規則是一致的。前端
因爲這個緣由,全局的變量一般放在根元素:root
裏面,確保任何選擇器均可以讀取它們。git
:root { --primary-color: #06c; }
同時, CSS 變量提供了 JavaScript 與 CSS 通訊的方法。就是利用 js 操做 css 變量。咱們可使用:github
<style> /* ...和上面 CSS 一致 */ </style> <!-- 呈現黃色字體 --> <div class="text-primary">red</div> <!-- 呈現藍色字體,main 選擇器定義的顏色 --> <main id='primary' class="text-primary">blue</main> <!-- 呈現紫色字體,當前內聯樣式表的定義 --> <div id="secondary" style='--primary-color: purple" class="text-primary">purple</main> <script> // 設置變量 document.body.style.setProperty('--primary-color', 'yellow'); // 設置變量,js DOM 元素 ID 就是全局變量,因此直接設置 main 便可 // 變爲 紅色 primary.style.setProperty('--primary-color', 'red'); // 變爲 黃色,由於當前樣式被移除了,使用 body 上面樣式 secondary.style.removeProperty('--primary-color'); // 經過動態計算獲取變量值 getComputedStyle(document.body).getPropertyValue('--primary-color') </script>
咱們能夠在業務項目中定義以及替換 CSS 變量,你們能夠參考 mvp.css。該庫大量使用了 CSS 變量而且讓你去根據本身需求修改它。瀏覽器
:root { --border-radius: 5px; --box-shadow: 2px 2px 10px; --color: #118bee; --color-accent: #118bee0b; --color-bg: #fff; --color-bg-secondary: #e9e9e9; --color-secondary: #920de9; --color-secondary-accent: #920de90b; --color-shadow: #f4f4f4; --color-text: #000; --color-text-secondary: #999; --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; --hover-brightness: 1.2; --justify-important: center; --justify-normal: left; --line-height: 150%; --width-card: 285px; --width-card-medium: 460px; --width-card-wide: 800px; --width-content: 1080px; }
咱們能夠看到基於 CSS 變量,能夠更友好的和設計師的設計意圖結合在一塊兒。也易於修改,在業務項目中合理使用無疑能夠事半功倍。安全
若是讓我來思考,我確定沒法想象出結合 CSS 預處理器 + CSS 變量即可以實現組件樣式的默認配置。這裏我先介紹兩個關於該功能的前置知識點:前端框架
事實上,CSS 變量的 var() 函數還可使用第二個參數,表示變量的默認值。若是該變量此前沒有定義或者是無效值,就會使用這個默認值。app
/* 沒有設置過 --primary-color,顏色默認使用 #7F583F */ color: var(--primary-color, #7F583F);
雖然目前 CSS 變量不是新的屬性,但終究不是全部的瀏覽器都支持 CSS 變量的,這裏咱們仍是要考慮一下優雅降級。框架
/* 對於不支持 CSS 變量的瀏覽器,能夠採用下面的寫法。*/ a { /* 顏色默認值 */ color: #7F583F; /* 不支持則不識別該條規則 */ color: var(--primary); }
結合 CSS 處理器 + CSS 變量即可以實現組件樣式的默認配置。這裏參考了有讚的 Vant Weapp 的作法。有贊代碼 theme.less 以下所示:
// 先導入全部 Less 變量 @import (reference) './var.less'; // 利用正則去替換 Less 變量 爲 CSS 變量 .theme(@property, @imp) { @{property}: e(replace(@imp, '@([^() ]+)', '@{$1}', 'ig')); @{property}: e(replace(@imp, '@([^() ]+)', 'var(--$1, @{$1})', 'ig')); }
函數效果以下所示:
@import '../common/style/theme.less'; .van-button { // ... 其餘省略 .theme(height, '@button-default-height'); .theme(line-height, '@button-line-height'); .theme(font-size, '@button-default-font-size'); } // => less 編譯以後生成 .van-button{ // ... 其餘省略 height:44px; height:var(--button-default-height,44px); line-height:20px; line-height:var(--button-line-height,20px); font-size:16px; font-size:var(--button-default-font-size,16px); }
咱們能夠看到每調用一次 Less 函數將會被編譯成兩個屬性。第一個屬性的設定對於不支持 CSS 變量的設備能夠直接使用,若是當前設備支持 CSS 變量,則會使用 CSS 變量,可是因爲當前 CSS 變量未定義,就會使用變量的默認值。雖然 '@button-default-height 雖然也是一個變量,可是該變量僅僅只是 less 變量,最終生成的代碼中並無 --button-default-height 這樣的變量。此時咱們就能夠在使用樣式的位置或者 :root 中添加變量 --button-default-height。
這種方式更適合組件開發,由於該方案不聲明任何 css 變量,只是預留的 css 變量名稱和默認屬性。這樣的話,不管開發者的選擇器優先度有多低,代碼均可以很容易的覆蓋默認屬性。由於咱們僅僅使用 css 的默認值。
你們可能有時候會想,這樣的話,咱們不是有更多的代碼了嗎?其實未必,事實上咱們能夠直接直接在頁面內部定義變量樣式。其餘組件直接經過 style 去使用頁面內的變量。固然了,事實上書寫的代碼多少,重點在於想要控制默認樣式的粒度大小。粒度越小,則須要在各個組件內部書寫的變量越多,粒度大,咱們也就沒必要考慮太多。
CSS 沒有邏輯切換彷佛是一種共識,可是咱們能夠利用選框(單選與多選)和 CSS 變量來實現判斷邏輯。咱們先來看看如何使用 CSS 變量。
<style> .red-box { --toggler: ; --red-if-toggler: var(--toggler) red; background: var(--red-if-toggler, green); /* will be red! */ } .green-box { --toggler: initial; --red-if-toggler: var(--toggler) red; background: var(--red-if-toggler, green); /* will be green! */ } </style> <!-- 寬度高度爲 100px 的 紅色盒子 --> <div style="height: 100px; width: 100px" class="red-box" ></div> <!-- 寬度高度爲 100px 的 綠色盒子 --> <div style="height: 100px; width: 100px" class="green-box" ></div>
這裏由於一個變量 --toggler 使用空格 或者 initial 而產生了不一樣的結果,基於這樣的結果咱們不難想象咱們能夠觸發變量的修改而產生不一樣的結果。
他不是一個 bug,也不是一個 hack。他的原理完徹底全的在 CSS Custom Properties 規範 中。
This value serializes as the empty string, but actually writing an empty value into a custom property, like --foo: ;, is a valid (empty) value, not the guaranteed-invalid value. If, for whatever reason, one wants to manually reset a variable to the guaranteed-invalid value, using the keyword initial will do this.
解釋以下,事實上 -foo: ; 這個變量並非一個無效值,它是一個空值。initial 纔是 CSS 變量的無效值。其實這也能夠理解,css 沒有所謂的空字符串,空白也不表明着無效,只能使用特定值來表示該變量無效。這個時候,咱們再回頭來看原來的 CSS 代碼。
.red-box { /* 當前爲空值 */ --toggler: ; /* 由於 var(--toggler) 獲得了空,因此獲得結果 爲 --red-if-toggler: red */ --red-if-toggler: var(--toggler) red; /** 變量是 red, 不會使用 green */ background: var(--red-if-toggler, green); /* will be red! */ } .green-box { /** 當前爲無效值 */ --toggler: initial; /** 仍舊無效數據,由於 var 只會在參數不是 initial 時候進行替換 */ --red-if-toggler: var(--toggler) red; /** 最終無效值沒用,獲得綠色 */ background: var(--red-if-toggler, green); /* will be green! */ /* 根據當前的功能,咱們甚至能夠作到 and 和 or 的邏輯 * --tog1 --tog2 --tog3 同時爲 空值時是 紅色 */ --red-if-togglersalltrue: var(--tog1) var(--tog2) var(--tog3) red; /* * --tog1 --tog2 --tog3 任意爲 空值時是 紅色 */ --red-if-anytogglertrue: var(--tog1, var(--tog2, var(--tog3))) red; }
當咱們須要開發響應式網站的時候,咱們必需要使用媒體查詢 @media。先看一下用傳統的方式編寫這個基本的響應式 CSS:
.breakpoints-demo > * { width: 100%; background: red; } @media (min-width: 37.5em) and (max-width: 56.249em) { .breakpoints-demo > * { width: 49%; } } @media (min-width: 56.25em) and (max-width: 74.99em) { .breakpoints-demo > * { width: 32%; } } @media (min-width: 56.25em) { .breakpoints-demo > * { background: green; } } @media (min-width: 75em) { .breakpoints-demo > * { width: 24%; } }
一樣,咱們能夠利用 css 變量來優化代碼結構,咱們能夠寫出這樣的代碼:
/** 移動優先的樣式規則 */ .breakpoints-demo > * { /** 小於 37.5em, 寬度 100% */ --xs-width: var(--media-xs) 100%; /** 小於 56.249em, 寬度 49% */ --sm-width: var(--media-sm) 49%; --md-width: var(--media-md) 32%; --lg-width: var(--media-gte-lg) 24%; width: var(--xs-width, var(--sm-width, var(--md-width, var(--lg-width)))); --sm-and-down-bg: var(--media-lte-sm) red; --md-and-up-bg: var(--media-gte-md) green; background: var(--sm-and-down-bg, var(--md-and-up-bg)); }
能夠看出,第二種 CSS 代碼很是清晰,數據和邏輯保持在一個 CSS 規則中,而不是被 @media 切割到多個區塊中。這樣,不但更容易編寫,也更加容易開發者讀。詳情能夠參考 css-media-vars。該代碼庫僅僅只有 3kb 大小,可是倒是把整個編寫代碼的風格修改的徹底不一樣。原理以下所示:
/** * css-media-vars * BSD 2-Clause License * Copyright (c) James0x57, PropJockey, 2020 */ html { --media-print: initial; --media-screen: initial; --media-speech: initial; --media-xs: initial; --media-sm: initial; --media-md: initial; --media-lg: initial; --media-xl: initial; /* ... */ --media-pointer-fine: initial; --media-pointer-none: initial; } /* 把當前變量變爲空值 */ @media print { html { --media-print: ; } } @media screen { html { --media-screen: ; } } @media speech { html { --media-speech: ; } } /* 把當前變量變爲空值 */ @media (max-width: 37.499em) { html { --media-xs: ; --media-lte-sm: ; --media-lte-md: ; --media-lte-lg: ; } }
繼 CSS 鍵盤記錄器 暴露了 CSS 安全性問題以後,CSS 變量又一次讓我看到了玩技術是怎麼樣的。CSS Space Toggle 技術不但能夠應用於上面的功能,甚至還能夠編寫 UI 庫 augmented-ui 以及 掃雷 遊戲。這簡直讓我眼界大開。在我有限的開發生涯中,難找到第二種相似於 css 這種設計意圖和使用方式差別如此之大的技術。
CSS 是頗有趣的,而 CSS 的有趣之處就在於最終呈現出來的技能強弱與你自身的思惟方式,創造力是密切相關的。上文只是介紹了 CSS 變量的一些玩法,也許有更多有意思的玩法,不過這就須要你們的創造力了。
若是你以爲這篇文章不錯,但願能夠給與我一些鼓勵,在個人 github 博客下幫忙 star 一下。
博客地址