預覽地址: Ant Design Themecss
const App = () => { return ( <ThemeProvider theme={{ name: 'dark', variables: { 'primary-color': '#00ff00' }, }} > <Button type="primary">Primary Button</Button> </ThemeProvider> ); };
開箱即用 antd-theme 歡迎 Star ❤git
CSS 變量github
mix(var(--primary-color), #fff, 20%)
多套 CSS 主題瀏覽器
less 動態切換antd
編譯時把不一樣皮膚須要修改的less變量、表達式留空, 在運行時填充。app
好比less
.btn { background: @primary-color; &:active { background: mix(@primary-color, white, 10%); } }
會編譯成異步
/* less-loader!./style.less */ .btn { background: "[theme:primaryColor,default:#1890ff]"; } .btn:active { background: "[theme:e8efafb1,default:#40a9ff]"; }
這種樣式是不能直接加載到頁面中的的, 因此會進一步處理成ide
/* themed-style-loader!./style.css */ var loadStyle = require('./load-themed-style').loadStyle; var css = ` .btn { background: "[theme:primaryColor,default:#1890ff]"; } .btn:active { background: "[theme:e8efafb1,default:#40a9ff]"; } `; loadStyle(css);
同時也會編譯出不一樣皮膚的替換變量,並注入到特定的文件 themes.js
函數
/* themes.js */ module.exports = { default: { primaryColor: '#1890ff', e8efafb1: '#40a9ff' }, dark: { primaryColor: '#1890ee', e8efafb1: '#40a9ee' }, compact: { primaryColor: '#1890dd', e8efafb1: '#40a9dd' } }
皮膚加載器大概長這樣
/* load-themed-style.js */ var themes = require('./themes.js') var styles = []; var loadStyle = function(css) { styles.push(css); applyStyles(); } var loadTheme = function(name) { applyStyles(themes[name]); } var applyStyles = function(variables) { var css = styles.join('').replace( /"\[theme:([\w]+),default:(\S+)\]"/, function(_, themeSlot, defaultValue){ return varialbes && varialbes[themeSlot] || defaultValue; } ); // 生成好的css插入到頁面中 } module.exports = { loadStyle, loadTheme }
最後調用 loadTheme('xx')
就能夠切換到對應的皮膚
插件若是分析到某個表達式依賴了須要實時修改的變量,就會把該表達式對應的 AST 注入到 themes.js
裏面
/* themes.js */ // @primary-color var expr1 = { type: 'Variable', name: '@primary-color' }; // mix(@primary-color, white, 10%) var expr2 = { type: 'Call', name: 'mix', args: [ { type: 'Variable', name: '@primary-color' }, { type: 'Color', rgb: [255, 255, 255], alpha: 255, }, { type: 'Dimension', value: 10, unit: '%' } ] }; module.exports = { default: { background: 'white', primaryColor: { expr: expr1, default: '#1890ff' }, e8efafb1: { expr: expr2, default: '#40a9ff' } }, dark: { background: 'black', primaryColor: { expr: expr1, default: '#1890ff' }, e8efafb1: { expr: expr2, default: '#1890ff' } }, ... }
loadTheme
會根據傳入的實時變量和皮膚裏面的 AST 計算出填充值,填充留空並應用修改
var loadTheme = function(name, runtimeVariables) { // 根據傳入實時變量計算出本次的皮膚變量 var themeVariables = compute( themes[name], runtimeVariables ); // 應用樣式 applyStyles(themeVariables); }
如今調用 loadTheme('xx', { 'primary-color': '#xxxxxx' })
就能夠實時的修改頁面主色調
ant-design
內部使用了 ~`colorPalette('@{background}', 7)`
內聯 Javascript 塊, 致使沒法跟蹤表達式的變量依賴, 因此在 less 解析以前對樣式代碼進行預處理,所有替換成 colorPalette(@background, 7)
並提供對應的 colorPalette 函數實現。
換膚方案是基於CSS屬性替換, 在全部皮膚下同一個組件生成出來的樣式須要行數一致且表達式hash一致。
// Mixin .button-color(@color) { color: @color; } .btn-primary { &:active { // CSS Guard 1 & when (@theme = dark) { .button-color(@primary-7); } // CSS Guard 2 & when not (@theme = dark) { .button-color(~`colorPalette('@{btn-primary-bg}', 7)`); } } }
上面的按鈕樣式在默認皮膚下會生成
.btn-primary:active { color: "[theme:primary7]"; }
暗黑模式下生成的倒是
/* sha1(colorPalette(@btn-primary-bg, 7))= 9ebde6df87d1def7be1e8e5c80144b793cb1e2c2 */ .btn-primary:active { color: "[theme:9ebde6df]"; }
這樣就會出現運行時變量填充錯誤,所以須要修改樣式解析後的AST。先對 Mixin 調用進行展開, 而後對 CSS Guards 進行轉換,在 AST 執行以前上面的按鈕樣式會被轉換成:
.btn-primary { &:active { color: if(@theme = darak, @primary-7); color: if(not @theme = dark, colorPalette(@btn-primary-bg, 7)); } }
轉換後的代碼就能夠愉快的用上文的方式進行處理了, 這裏產生的多個color定義會在運行時拿到皮膚變量後進行刪除處理。
遞歸 Mixin Call 的循環變量不能做爲皮膚變量, 好比 ant-design
柵格系統相關代碼中的 @grid-columns
.loop-grid-columns(@index, @class) when (@index > 0) { // ... .@{ant-prefix}-col@{class}-order-@{index} { order: @index; } .loop-grid-columns((@index - 1), @class); } .loop-grid-columns(@grid-columns, @class);
postcss-position 會直接對 css 中的 position 屬性值進行 value.match(/^static|absolute|fixed|relative.../).toString()
,
而 '"[theme:position,default:relative]"'.match(...) === null
所以會出如今編譯過程當中的報錯,具體的 postcss-position 代碼在 這裏
支持 CSS Variable Backend 配置
開啓 CSS Variable Backend 後樣式文件會編譯成
/* less-loader!./style.less */ .btn { background: var(--primaryColor, #1890ff); } .btn:active { background: var(--e8efafb1, #40a9ff); }
applyStyle
的內部實現調整爲 style.setProperty(--primaryColor, '#xxxxxx')
採用 React Fiber
相似的方案對樣式的渲染過程進行異步處理,避免過多樣式同步渲染致使的頁面卡頓