当前位置: 首页 > Web前端 > CSS

更完美的Ant-Design动态换肤方案

时间:2023-03-31 00:50:09 CSS

前言预览地址:AntDesignThemeconstApp=()=>{return(主按钮);};开箱即用antd-themeWelcomeStar?现有方案缺陷CSS变量不能支持复杂表达式mix(var(--primary-color),#fff,20%)浏览器兼容性有限。多套CSS主题具有固定的主题样式,无法实时切换主题颜色。CSS代码分离不易处理less动态切换需要引入的runtime少实现了提供接口,支持实时修改@primary-color,@border-radius-base等参数浏览器页面刷新后,默认样式为切换后的样式。没有多余的动画和明显的加载过程。打包体积小,无需打包多个样式文件。代码分离友好,编译时可以分别加载不同页面的样式,实现不同的皮肤。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]";}这种样式不能直接加载到页面中,所以会进一步处理成/*themed-style-loader!./style.css*/varloadStyle=require('./load-themed-style').loadStyle;varcss=`.btn{background:"[theme:primaryColor,default:#1890ff]";}.btn:active{background:"[theme:e8efafb1,default:#40a9ff]";}`;加载样式(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*/varthemes=require('./themes.js')varstyles=[];变量加载样式=funfunction(css){styles.push(css);applyStyles();}varloadTheme=function(name){applyStyles(themes[name]);}varapplyStyles=function(variables){varcss=styles.join('').replace(/"\[主题:([\w]+),default:(\S+)\]"/,function(_,themeSlot,defaultValue){returnvarialbes&&varialbes[themeSlot]||defaultValue;});//将生成的css插入到页面中}module.exports={loadStyle,loadTheme}最后调用loadTheme('xx')切换到对应的皮肤变量,实时修改插件。如果分析了一个表达式的依赖关系,需要实时修改变量,就会把表达式对应的AST注入到themes.js中/*themes.js*///@primary-colorvarexpr1={type:'Variable',name:'@primary-color'};//mix(@primary-color,white,10%)varexpr2={type:'Call',name:'mix',args:[{type:'变量',name:'@primary-color'},{type:'Color',rgb:[255,255,255],alpha:255,},{type:'Dimension',值e:10,unit:'%'}]};module.exports={default:{background:'white',primaryColor:{expr:expr1,default:'#1890ff'},e8efafb1:{expr:expr2,默认值:'#40a9ff'}},dark:{background:'black',primaryColor:{expr:expr1,default:'#1890ff'},e8efafb1:{expr:expr2,default:'#1890ff'}},...}loadTheme会根据传入的实时变量和皮肤中的AST计算填充值,填充留空并应用修改varloadTheme=function(name,runtimeVariables){//根据传入实时变量varthemeVariables=compute(themes[name],runtimeVariables);//应用样式applyStyles(themeVariables);}现在调用loadTheme('xx',{'primary-color':'#xxxxxx'})实时修改页面主色遇到的问题colorPalette函数ant-design内部使用了~`colorPalette('@{background}',7)`内联Javascript块,导致无法追踪表达式的变量依赖,所以在less解析前更改样式代码进行预处理,全部替换为colorPalette(@background,7)并提供相应的colorPalette函数实现Mixins扩展&CSSGuards转换换肤解决方案基于CSS属性替换。同一组件在所有皮肤下生成的样式需要具有相同的行数和相同的表达式散列。//Mixin.button-color(@color){color:@color;}.btn-primary{&:active{//CSSGuard1&when(@theme=dark){.button-color(@primary-7);}//CSSGuard2&如果不是(@theme=dark){.button-color(~`colorPalette('@{btn-primary-bg}',7)`);}}}abovebuttonstyle在默认皮肤下,.btn-primary:active{color:"[theme:primary7]";}在深色模式下生成/*sha1(colorPalette(@btn-primary-bg,7))=9ebde6df87d1def7be1e8e5c80144b793cb1e2c2*/.btn-primary:active{color:"[theme:9ebde6df]";}这样会出现运行时变量填充错误,所以需要修改样式解析后的AST。先扩展Mixin调用,再转换CSSGuards。在执行AST之前,上面的按钮样式会被转换为:.btn-primary{&:active{color:if(@theme=darak,@primary-7);颜色:if(not@theme=dark,colorPalette(@btn-primary-bg,7));}}转换后的代码可以愉快的按照上面的方式处理,这里生成的多重颜色定义在运行时获取皮肤变量后会被删除。限制递归MixinCall的循环变量不能作为皮肤变量,比如@grid-columns.loop-grid-columns(@index,@class)when(@index>0){//ant-design网格系统相关代码....@{ant-prefix}-col@{class}-order-@{index}{order:@index;.loop-grid-columns((@index-1),@class);}.循环网格列(@网格列,@class);postcss-position不兼容postcss-position会直接执行value.match(/^static|absolute|fixed|relative.../).toString(),and'[theme:position,default:relative]”'。match(...)===null所以编译时会报错。具体的postcss-position代码在这里What'sNext支持CSSVariableBackend配置。启用CSSVariableBackend后,样式文件将编译成/*less-loader!./style.less*/.btn{background:var(--primaryColor,#1890ff);}.btn:active{background:var(--e8efafb1,#40a9ff);}applyStyle内部实现调整为style.setProperty(--primaryColor,'#xxxxxx')requestIdleCallback采用类似ReactFiber的方案异步处理样式渲染过程,避免过多多样式同步渲染导致的页面卡顿