自定義主題色實現(xiàn)之 CSS Variable

背景

  • 需求背景:動態(tài)配置頁面主題相關(guān)顏色,而不是定制主題模式(類似黑夜模式,白天模式)。
  • 兼容性問題:在 IE 中 var()報錯顯示異常成透明色,(css-vars-ponyfill 插件 夕土,使頁面展示一個默認色)
  • 第三方 UI 庫支持 CSS Variable,(antd@4.17.0-alpha.0 以上瘟判,mkui-fd@4.1.8-beta 以上 )

1. 實現(xiàn)原理

1.1. 自定義屬性 (--*): CSS 變量

帶有前綴 -- 的屬性名怨绣,比如--example--name,表示的是帶有值的自定義屬性拷获,其可以通過var 函數(shù)在全文檔范圍內(nèi)復(fù)用的篮撑。

CSS 自定義屬性是可以級聯(lián)的:每一個自定義屬性可以多次出現(xiàn),并且變量的值將會借助級聯(lián)算法和自定義屬性值運算出來匆瓜。

注意赢笨,規(guī)則集所指定的選擇器定義了自定義屬性的可見作用域。通常的最佳實踐是定義在根偽類:root 下驮吱,這樣就可以在 HTML 文檔的任何地方訪問到它茧妒。

:root 這個 CSS 偽類匹配文檔樹的根元素。對于 HTML 來說左冬,:root 表示 <html> 元素桐筏,除了優(yōu)先級更高之外,與 html 選擇器相同拇砰。

1.2. ConfigProvider.config() (config-provider/index.js)
  ConfigProvider.config = setGlobalConfig;
  var setGlobalConfig = function setGlobalConfig(_ref) {
  var prefixCls = _ref.prefixCls,
        iconPrefixCls = _ref.iconPrefixCls,
        theme = _ref.theme;

  if (prefixCls !== undefined) {
     globalPrefixCls = prefixCls;
  }

  if (iconPrefixCls !== undefined) {
     globalIconPrefixCls = iconPrefixCls;
  }

  if (theme) {
     (0, _cssVariables.registerTheme)(getGlobalPrefixCls(), theme); // 更改主題色
  }
1.3. registerTheme() (node_modules/mkui-fd/lib/config-provider/cssVariables.js)

創(chuàng)建所有 css 變量梅忌,可參考簡易版 customCssVariable.js

import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import { TinyColor } from '@ctrl/tinycolor';
import { generate } from '@ant-design/colors';

function setThemeColor({ themeColor, varName }) {
  const variables = {};

  // ================ Primary Color ================
  if (themeColor) {
    // 轉(zhuǎn)成 TinyColor 色值格式
    const primaryColor = new TinyColor(themeColor);
    // 10個不同階梯色值
    const colorPalettes = generate(primaryColor.toRgbString());
    // Legacy - We should use semantic naming standard
    variables[`${varName}`] = themeColor;
    colorPalettes.forEach((color, index) => {
      variables[`${varName}-${index + 1}`] = color;
    });
  }

  // Convert to css variables
  // cssList ['--theme-color: xxx','--theme-color-1: xxx',...,'--theme-color-10: xxx' ]
  const cssList = Object.keys(variables).map(
    key => `--${key}: ${variables[key]};`,
  );

  // updateCSS 更新業(yè)務(wù)代碼中的主題色: 創(chuàng)建 style 標簽,值為cssList除破,并作為最后一個子元素插在head標簽下
  updateCSS(
    '\n  :root {\n    '.concat(cssList.join('\n'), '\n  }\n  '),
    '-mkui-fd-'.concat(Date.now(), '-').concat('-dynamic-theme'),
  );
}
1.4. updateCSS() (node_modules/rc-util/lib/Dom/dynamicCSS.js)

創(chuàng)建 style 標簽牧氮,值為 cssList,作為最后一個子元素插在 head 標簽下瑰枫,詳見 node_modules/rc-util/lib/Dom/dynamicCSS.js

1.5. 動態(tài)改變顏色另外一種寫法(我們項目中不推薦)

在 body 標簽中增加 style 屬性踱葛,改變 body 中所有 --mkui-primary-color 使用值, 優(yōu)先級高于 html

document.body.style.setProperty('--mkui-primary-color', '#ffff00');

        ↓ ↓ ↓ ↓ ↓ ↓

<body style="--mkui-primary-color:#ffff00;">

2. 如何使用

2.1 替換當(dāng)前項目引入樣式文件為 CSS Variable 版本,并在 .babel.config 中去除 babel-plugin-import 配置躁垛。

{ "libraryName": "antd" }

     import { Button } from 'antd';

     ReactDOM.render(<Button>xxxx</Button>);

           ↓ ↓ ↓ ↓ ↓ ↓

     var button = require('antd/lib/button');
     ReactDOM.render(<button>xxxx</button>);

{ "libraryName": "antd", style: "css" }

     import { Button } from 'antd';

     ReactDOM.render(<Button>xxxx</Button>);

           ↓ ↓ ↓ ↓ ↓ ↓

     var button = require('antd/lib/button');
     require('antd/lib/button/style/css');
     ReactDOM.render(<button>xxxx</button>);

{ "libraryName": "antd", style: true }

     import { Button } from 'antd';

     ReactDOM.render(<Button>xxxx</Button>);

           ↓ ↓ ↓ ↓ ↓ ↓

     var button = require('antd/lib/button');
     require('antd/lib/button/style');
     ReactDOM.render(<button>xxxx</button>);

注意:

Antd 默認支持基于 ES modules 的 tree shaking剖毯,對于 js 部分,直接引入 import { Button } from 'antd' 就會有按需加載的效果教馆。

如今webpack,rollup等構(gòu)建工具都具備了搖樹功能(Tree Shaking), 其原理是利用ESM模塊的import語法的特性,通過AST語法樹進行分析然后去除未使用到的代碼擂达。但tree shaking方式也只是處理了js部分土铺,對于組件的css加載還需要手動引入.

2.2 引入包含css 變量的組件庫 css 文件(app.js)
import 'mkui-fd/dist/mkui-fd.variable.min.css';
import 'mkui-ext/dist/mkui-ext.variable.min.css';
2.3.定義顏色變量(variables.less)
  1. 使用 mkui-fd 組件中已有變量

:root 下已經(jīng)默認生成了部分可全局使用的變量:--mkui-primary-1, ... --mkui-primary-10

使用 var() 插入 CSS 變量的值

     @theme-color: var(--mkui-primary-color, #1890ff); 

     @theme-color-selected-bg: var(--mkui-primary-1, #f0f8ff);
  1. 自定義創(chuàng)建可全局使用的 CSS 變量

    // color.less

    為了兼容 IE 使用 css-vars-ponyfill, 必須使用:root 而不是 html

    :root {
    --theme-color: #1890ff;
    --theme-color-selected-bg: #f0f8ff;
    }

  // variables.less

  @theme-color: var(--theme-color);

  @theme-color-selected-bg: var(--theme-color-selected-bg, #f0f8ff);
2.4. 調(diào)用配置方法

1) 調(diào)用 ConfigProvider 配置方法設(shè)置主題色(cssVariable.js):

   // 修改 mkui-fd 組件顏色
   ConfigProvider.config({
     theme: { primaryColor: themeColor },
  });
  
  // 修改 mkui-ext 組件顏色
   ExtConfigProvider.config({
    theme: { primaryColor: themeColor },
  });

Note: primaryColor: 組件基本色胶滋,successColorwarningColor悲敷,errorColor究恤,infoColor特定情況下的顏色。

2) 對于自定義顏色變量后德,動態(tài)更改自定義變量顏色(customCssVariable.js)

setThemeColor({ 
    themeColor, 
    varName: 'theme-color' 
})
2.5. 色值相關(guān)
  • 與主題色相關(guān)的顏色部宿,設(shè)計圖中會給一個主色(C6),及對應(yīng)的10個色階階梯的哪一階

3. 注意點:

  1. 為什么是:root 而不是 html

    • CSS 不僅用于樣式化 HTML 文檔, 它也用于 XML 和 SVG 文件瓢湃。對于 XML 和 SVG 文件理张,:root 不是選擇 html元素,而是選擇它們的根(例如 SVG 文件中的 svg 標簽)绵患。

    • :root 選擇器優(yōu)先級更高

    • 有助于將使用的 CSS 變量與項目中使用樣式的選擇器分開

    • css-vars-ponyfill 中要求:自定義屬性聲明支持僅限于:root:host 規(guī)則集

  2. fade()雾叭,dark()等函數(shù)不支持變量參數(shù)

      fade('1199ff', 90%);
      fade(var(--theme-color), 90%); // 錯誤使用
    
  3. mkui-fd, mkui-ext以及項目中顏色相關(guān)樣式,不要使用內(nèi)聯(lián)和 !important

  1. IE 11下取色器組件 react-color@2.19.3報錯落蝙,降低react-color@2.18.1組件版本
    SCRIPT438: Object doesn't support property or method 'contains'

4. 其他擴展點

4.1 polyfill 和 ponyfill 的區(qū)別

polyfill 在原有的墻壁上打補丁织狐,ponyfill 的策略則是另起爐灶,不會在原有的墻壁上修補筏勒,而是重新建一面墻移迫,保證原來的墻壁還是原始純凈無污染。

例如:Array.isArray(), 此方法 IE8 瀏覽器并不支持

polyfill 策略

  if (!Array.isArray) {
     Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === '[object Array]';
     };
  }

ponyfill 策略

  // 避免使用原生 API
  // 基本上管行,為了避免全局命名的污染厨埋,Ponyfill都是建議采用獨立的模塊化的方式開發(fā)與調(diào)用的
  function isArray (arg) {
     return Object.prototype.toString.call(arg) === '[object Array]';
  }

什么時候使用 Ponyfill?

  1. 有些原生 API 完全沒法模擬,此時只能使用 Ponyfill 策略病瞳,例如 IE9 瀏覽器無法模擬 indexDB 能力揽咕,history.pushState()方法
  2. 有些原生 API 規(guī)范還沒穩(wěn)定,或者處于快速迭代中套菜,或者是瀏覽器部分支持
4.2 css-vars-ponyfill 的使用

作用:

  • CSS 自定義屬性到靜態(tài)值的轉(zhuǎn)換
  • 現(xiàn)代和舊版瀏覽器中運行時值的實時更新
  • 轉(zhuǎn)換 <link>亲善、<style>@import CSS
  • 將相對 url()路徑轉(zhuǎn)換為絕對 URL
  • 支持鏈式和嵌套的 var()函數(shù)
    ...

限制:

  • 自定義屬性聲明支持僅限于 :root:host 規(guī)則集
    • var() 的使用僅限于屬性值(根據(jù) W3C 規(guī)范)

更新值:

  • 在舊版瀏覽器中,ponyfill 將確定哪些 <link><style>中包含 css 變量逗柴,然后轉(zhuǎn)換成與舊版兼容的 CSS蛹头,并附加到每個元素的 DOM。
  • 在支持 CSS 變量的現(xiàn)代瀏覽器中戏溺,ponyfill 將使用style.setProperty() 接口更新值渣蜗。
    請注意,當(dāng) options.onlyLegacyfalse 時旷祸,支持 CSS 變量的現(xiàn)代瀏覽器將被視為舊版瀏覽器耕拷。

Note:
{ onlyLegacy: true }將此值設(shè)置為 false可以在現(xiàn)代瀏覽器中模仿舊版瀏覽器,進行測試和調(diào)試托享。

4.3 HSV


基于HSB模型構(gòu)建色彩體系

1.色相

表示色彩的相貌骚烧,也就是我們常說的紅浸赫、橙、黃赃绊、綠等顏色名稱既峡。色相值按位置度量,取值為0°~360°碧查,在HSB色彩模型中紅色為0°运敢,黃色為60°,綠色為120°忠售,青色為180°传惠,藍色為240°,品紅色為300°档痪。十二色相環(huán)每一色相間距30°涉枫,二十四色相環(huán)每一色相間距15°。

2.飽和度

表示色彩的純度腐螟,取值范圍0~100%愿汰,從色環(huán)中心向外遞增。當(dāng)飽和度為0時點在中心乐纸,則顯示為灰衬廷、白、黑無彩色汽绢。當(dāng)飽和度達到100%時吗跋,點則移動到色環(huán)邊緣,會顯示每個色相最純的色光宁昭。如下圖所示跌宛,在色相(H)、亮度(B)不變的情況下減少飽和度(S)顏色逐漸變淡最后變成白色积仗。S控制混入白色的量疆拘。

3.亮度

指色彩的明亮度,取值范圍0~100%寂曹,沿著圓柱體底部向上遞增哎迄。亮度為0時即黑色,點處于最底部隆圆。當(dāng)達到100%時點上升到頂端漱挚,會顯示色相最鮮明的狀態(tài)。如下圖所示渺氧,在色相(H)旨涝、飽和度(S)不變的情況下減少亮度(B)顏色逐漸變暗最后變成黑色。B控制混入黑色的量侣背。

4.結(jié)論

1. 選取一個顏色作為主色(6 號色)颊糜;
2. 判斷減淡或加深哩治,進行顏色混合
    - 若減淡秃踩,則主色與純白色(#fff)混合衬鱼,根據(jù)色號,獲取貝塞爾曲線上的對應(yīng)值憔杨。
    - 若加深鸟赫,則主色與它對應(yīng)的深色混合,根據(jù)色號消别,獲取貝塞爾曲線上的對應(yīng)比例值抛蚤。加深時主色對應(yīng)的深色進行了明度與色相的調(diào)整,其中對色相的調(diào)整也就是上述引用中說的“針對冷暖色的旋轉(zhuǎn)”寻狂;
3. 分別取1~9色號的色值岁经,得到一條完整漸變色板。

參考文章

AntD 動態(tài)主題

基于 HSB 模型構(gòu)建色彩體系

Ant Design 色板生成算法演進之路

polyfill 和 ponyfill

css-vars-ponyfill 文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛇券,一起剝皮案震驚了整個濱河市缀壤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纠亚,老刑警劉巖塘慕,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒂胞,居然都是意外死亡图呢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門骗随,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛤织,“玉大人寸痢,你說我怎么就攤上這事儿礼。” “怎么了慌盯?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵牡昆,是天一觀的道長姚炕。 經(jīng)常有香客問我,道長丢烘,這世上最難降的妖魔是什么柱宦? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮播瞳,結(jié)果婚禮上掸刊,老公的妹妹穿的比我還像新娘。我一直安慰自己赢乓,他們只是感情好忧侧,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布石窑。 她就那樣靜靜地躺著,像睡著了一般蚓炬。 火紅的嫁衣襯著肌膚如雪松逊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天肯夏,我揣著相機與錄音经宏,去河邊找鬼。 笑死驯击,一個胖子當(dāng)著我的面吹牛烁兰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播徊都,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼沪斟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了暇矫?” 一聲冷哼從身側(cè)響起主之,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袱耽,沒想到半個月后杀餐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡朱巨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年史翘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冀续。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡琼讽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出洪唐,到底是詐尸還是另有隱情钻蹬,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布凭需,位于F島的核電站问欠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏粒蜈。R本人自食惡果不足惜顺献,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望枯怖。 院中可真熱鬧注整,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至椒袍,卻和暖如春驼唱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背槐沼。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工曙蒸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岗钩。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像肖油,于是被迫代替她去往敵國和親兼吓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容