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

如何使用cssvar做深色模式解决方案

时间:2023-03-31 13:25:38 CSS

本文主要背景:希望使用css变量实现深色模式和浅色模式的切换。原来的项目都是以less的形式定义的css,还有淡入淡出等less的功能,不想手动改less的功能,希望插件能支持less的解析功能,需要支持部分非开关模式,比如某个区域是固定灯模式。提供字段用于转换,只需要添加一个配置项,就是globalVars属性。你可以查看示例代码参考{loader:'less-loader',options:{globalVars:LessGlobalCSSVars,}}LessGlobalCSSVars看起来像这样white":"var(--static-white)",...}less会将LessGlobalCSSVars的映射关系添加到less文件中,查找变量时将其替换为对应的css变量。比如下面的lessfilediv{color:@bg-body;}less其实就是把文件的内容解析为bg-body:"var(--bg-body)";static-white:"var(--static-white)"div{color:@bg-body;}最后会把上面的文件编译成div{color:var(--bg-body);}(这是为什么,没有定义less变量,但是使用css变量编译less文件还没有会报错)第二步:如何分析less函数?[使用less插件]但是还有一个问题:less函数应该如何解析?比如fade(@bg-body,20%),如果不做处理,这个函数会抛出异常,因为var(--bg-body)不是less可以解析的节点类型,而它会提示var(--bg-body)无法转换为Color类型(less的一个节点类型),这是less的语法树分析,fade函数的第一个参数需要解析成Color节点类型,否则会抛出异常。因此,我们需要重写less的功能,具体是通过less的插件。如下修改less-loader的配置,添加一个插件。{loader:'less-loader',options:{globalVars:LessGlobalCSSSVars,plugins:[newLessSkipVarsPlugin()]}}这个插件的主要功能是重写不太具体的功能!!!less的所有功能都会注册到functions中,插件对外暴露功能,因此可以通过修改相应的less功能实现功能覆盖。插件的实现源码如下。functions对象是函数名到函数体的映射,所以我们可以将需要重写的函数重新设置为自己自定义的函数。函数的计算结果由两个css函数calc和var以及css变量表示,可以根据css变量在页面进行实时计算!以下只是我们支持的两个功能——淡化和变暗。fade用rgba函数表示,darken用hsl函数表示。rgba表示不能用css支持的函数表示,所以我们使用了hsl函数。这里可以看到我们需要一些特殊的css变量,比如--bg-body-SA、--bg-body-raw、--bg-HS、--bg-body-L,这些变量主要来源于原始变量被转换。我们使用原始颜色值(bg-body)进行转换(以下代码省略,主要是为了说明){if(color.type==='Call'){if(color.name==='var'){constkey=color.args[0].value.substring(2);返回`rgba(var(--${key}-raw),calc(var(--${key}-SA)*${parseLessNumber(percent)}))`;}}......return`rgba(${red},${green},${blue},${alpha*parseLessNumber(percent)})`;});功能。add('darken',function(color,amount,method){....if(color.type!=='Color')thrownewError(`fade函数参数类型错误:颜色除外,获取${color.type}`);consthsl=(newColor(color.rgb,color.alpha)).toHSL();if(typeofmethod!=='undefined'&&method.value==='relative'){hsl.l=hsl.l*(1-parseLessNumber(数量));}else{hsl.l=hsl.l-parseLessNumber(数量);}返回`hsl(${hsl.h},${hsl.s},${hsl.l})`;})}}编译插件后,我们使用fade函数编译的代码会转化为css变量表示第三步:如何支持局部光模式?【使用postcss插件】可以在dom节点上添加classname前缀,标记dom下的样式使用静态亮色模式,不随主题切换。这里需要做的主要分为3步:1.第一步:【添加dom前缀classname】给对应的dom节点添加classname前缀,比如static-light;//原来的dom结构aaa

//新的dom结构aaa
2。第二步:【添加前缀样式】生成样式时,通过postcss为所有样式添加static-light前缀;这一步实际上是在css-loader进程中添加postcss插件,为每条规则生成额外的静态样式。比如我定义了下面的lessstyle.test{background-color:@static-white;}经过postcss插件后,生成的产品会变成.test{background-color:var(--static-white);}.static-light.test{background-color:var(--static-white);}那么我们如何添加和生成这样的css呢?可以看到在css-loader的源码中,节点已经通过postcss插件进行了处理。我们只需要将我们的插件添加到插件列表中即可。result=awaitpostcss([...plugins,newcolorPlugin({staticEx:{prefix:'.static-light'},})]).process(content,{...});所以我们可以实现我们的postcss插件node,type){letstaticNode;switch(node.type){......case'rule':staticNode=node.clone();staticNode.selectors=staticNode.selectors.map(i=>{返回`${options.staticEx.prefix}${i}`});中断;默认:中断;}返回静态节点;}返回函数(css){让last=[];css.each((node,type)=>{conststaticNode=processNode(node,type);if(staticNode){last.push(staticNode);}});css.nodes=css.nodes.concat(last);};});主要实现思路是:通过当前节点clone一个相同的节点,最后返回时拼接该节点,使其生成两种样式;对于克隆的节点,添加一个选择器,staticNode.selectors=staticNode.selectors.map(i=>{return`${options.staticEx.prefix}${i}`});实现附加节点和本地css变量定义注意:css-loader会对参数进行校验,所以如果需要修改传入参数的格式,还需要修改options.json和normalizeOptions。第3步:插入一组指定类名的cssvar变量(此处为static-light)。这里我们使用webpack插件来实现。详见下篇第四步:添加全局css变量定义【webpack插件】我们定义css变量即可生效。添加@media(prefers-color-scheme:dark)可以在系统模式变化时切换css变量,实现样式切换。:root{--bg-body:"#1f1f1f";--static-white:'#fff'}@media(prefers-color-scheme:dark){:root{--bg-body:"#2f2f2f";--static-white:'#fff'}}在上面的部分中,我们还需要添加本地光样式对应的css变量,需要在上述变量的基础上添加如下一段代码。:root{--bg-body:"#1f1f1f";--static-white:'#fff'}@media(prefers-color-scheme:dark){:root{--bg-body:"#2f2f2f";--static-white:'#fff'}}.static-light{--bg-body:"#1f1f1f";--static-white:'#fff'}这会让我们手动添加变量变得复杂,而且容易出错,所以我们可以使用webpack插件来追加,webpack提供了多种hooks,我们可以使用这些生命周期钩子在正确的时间执行相应的逻辑。第一步:【生成css文件】我们需要保证生成的css文件只会被执行一次,并且在生成的文件插入到link标签之前,HtmlWebpackPlugin插件提供的生命周期钩子函数alterAssetTags返回所有当前资源列表。这会附加一些资源链接,因此我们可以在此生命周期挂钩处触发文件生成。compiler.hooks.compilation.tap('LarkThemePlugin',compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('LarkThemePlugin',(data,cb)=>{constsource=xxx;compilation.assets['theme.css']={source:()=>source,size:()=>Buffer.byteLength(source,'utf-8')};cb(null,data);})})第二步:生成链接tagreference:上一步生成的css资源文件。我们需要在html中添加一个link标签来引用css资源。在实际应用中,我们经常会在html中插入很多资源标签,我们希望标签能够在所有资源文件加载前插入compiler.hooks.compilation.tap('LarkThemePlugin',compilation=>{).alterAssetTags.tapAsync('LarkThemePlugin',(data,cb)=>{const{assetTags:{styles}}=data;styles.unshift({tagName:'link',voidTag:true,attributes:{href:'theme.css',rel:'stylesheet'}})cb(null,data);})})完整的webpack插件代码如下:constfs=require('fs');constpath=require('路径');constHtmlWebpackPlugin=require('html-webpack-plugin');const{getRootCSSVarMap}=require('../util');classInjectThemeWebpackPlugin{构造函数({lessVarsSet,darkTokens,lightTokens}){this.darkTokens=darkTokens;this.lightTokens=lightTokens;}//生成css变量的css样式generateResult(){constgenerateCss=(cssObj)=>{letcss='';for(letkeyincssObj){constvalue=cssObj[key];css+=`${key}:${value};`}return`:root{${css}}`;constdarkCSSObj=getRootCSSVarMap(this.darkTokens,'DARK');constlightCSSObj=getRootCSSVarMap(this.lightTokens,'LIGHT');返回`${generateCss(lightCSSObj)}\n@media(prefers-color-scheme:dark){${generateCss(darkCSSObj)}}`;}apply(compiler){//添加链接标签compiler.hooks.compilation.tap('LarkThemePlugin',compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tapAsync('LarkThemePlugin',(data,cb)=>{constsource=this.generateResult();compilation.assets['theme.css']={source:()=>source,size:()=>Buffer.byteLength(source,'utf-8')};const{assetTags:{样式}}=数据;styles.unshift({tagName:'link',voidTag:true,attributes:{href:'theme.css',rel:'stylesheet'}})cb(null,data);})})}}module.exports=InjectThemeWebpackPlugin;多仓库需要保证node_modules中只有一个html-webpack-plugin插件,否则会出现无法添加link标签的情况小伙伴们会有点陌生,可以学习理解下面的内容,以便您可以轻松完成有关如何使用每个插件的信息。一个less插件的例子less。.webpackjs.com/api...html-webpack-plugin生命周期钩子附录https://github.com/webpack-co...https://www.postcss.com.cn/ap...