预览地址: 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
相似的方案对样式的渲染过程进行异步处理,避免过多样式同步渲染致使的页面卡顿