日常记录,Xmind不够记录的了
之前的项目采用的是硬编码,维护两套css文件,切换主题时,引不同的css文件进行换肤。之后需要支持新的主题色,由此引出了动态换肤这个需求。记录下自己尝试的方案
方案一:采用css变量与less变量结合的方式开发
大概思路:
1. 项目中的元素色值使用less全局变量
2.less全局变量值使用css变量控制
3.在切换主题的时候,修改css变量,导致主题换肤的效果
基于以上的思路,现在说下具体的代码实现
- 全局less变量
第一步:配置less全局变量,在项目各个元素进行使用,配置方式有一下两种
1. 使用vuecli插件 ,vue-cli-plugin-style-resource-loader
vue add style-resource-loader
vue.config.js:
const path=require("path")
module.exports={
...
pluginOptions:{
'style-resource-loader':{
'preProcessor':'less',
patterns:[path.resolve(__dirname,".src/common/common.less"/*公共less变量文件*/)]
}
}
}
2. 使用sass-resources-loader
yarn add style-resources-loader
vue.config.js:
module.exports={
...
chainWebpack:config=>{
config.moudule.rule("less").oneOfs.store.forEach(item=>{
item
.use("sass-resources-loader")
.loader("sass-resources-loader")
.options({
resources:'./src/common/common.less'
})
})
}
}
第二步:说下全局less文件与css变量
- css变量配置文件
cssModel.js :
//这里主要是维护不同主题的色值
export const cssTheme={
themeOne:{
btn_P1:'#a62b15' ,
btn_P2:'#63b3f8',
....
},
themeTwo:{
btn_P1:'#ff846c' ,
btn_P2:'#63b3f880',
....
},
//之后可以根据需求配置自己的主题色,只要注意不同主题下,key值统一就行了
}
- 全局less变量文件
common.less
@btn_P1:var(--btn_P1 , #a62b15)
@btn_P2:var(--btn_P2 , #63b3f8)...
//var(自己定义的css变量,css变量默认值)预设一个css变量默认值,使刚进入页面的时候不要因为还没有初始化css变量完成导致页面“闪一下”
第三步:切换主题时,改变全局css变量,达到换肤目的:
这里只说下最重要的换肤逻辑,具体项目中会结合状态管理,实现存储用户切换的主题
changeSkin.js
import {cssTheme} from 'cssModel.js'
export const initTheme=(theme="themeOne")=>{
let themeTemp=theme;
...
//省略的逻辑:通过状态管理或storage之类的状态改变这个themeTemp
changeTheme(themeTemp)
}
export const changeTheme=(theme="themeOne")=>{
//这里获取到对应的主题json
const cssTheme=cssTheme[theme]
...
//省略的逻辑:更新主题状态并存储
changeCssVar(cssTheme)
}
const changeCssVar=cssTheme=>{
for(let key of cssTheme){
document.getElementsByTagName("body")[0].style.setProperty(`--${key}`,cssTheme[key])}}
最后在组件中使用less变量就可以了
<componentA />内部less:
<style lang="less" scope>
.btnPrimary{
background:@btn_P1
}
</style>
以上,第一种方案的逻辑走完了。项目初始化的时候,initTheme初始化css变量,换肤的时候,changeTheme传入在cssModel.js中定义的主题名称,改变css全局变量。
需要注意的是:与UI同学敲定色值的时候,一定要说清楚,
- 一个编码(cssModel.js中主题下的key值)对应N个色值,开发到一半时,想要修改某个主题下某个按钮的色值,只能新增编码,否则会影响其他使用这个编码的元素
- 同一个元素,只能使用一个编码,不可能说换肤的时候,改变该元素的色值编码。如果发现当前使用的编码在某个主题下色值不太好,如果影响范围小,直接修改色值,影响大,新增编码
对于方案一个的补充:兼容性
因为IE浏览器是完全不兼容css变量的,如果有IE需求,可以使用cssVars兼容处理,不过也有弊端,先说怎么兼容:
import cssVars from "css-vars-ponyfill"
//在changeSkin.js中,changeTheme方法改写,不去调用changeCssVar,直接使用cssVars处理
export const changeTheme= (theme='themeOne') => {
cssVars({
preserveStatic:false,
watch: true,
variables: cssTheme[theme],
onlyLegacy: false
};
- 参数说明
watch:当添加,删除或修改其或元素的禁用或href属性时,ponyfill将自行调 用
variables :自定义的 [变量名,色值] 键值对
onlyLegacy :false--默认将css变量编译为浏览器识别的css样式 true--当浏览器不支持css变量的时候将css变量编译为识别的css
preserveStatic:只会将样式中的颜色部分插入到文档中,增强了性能
- cssVars原理
1. 将页面中的样式进行分类,如果link或者style中的样式没有包含css自定义变量,就将标签的data-cssvars属性赋值为 "skip";如果包含了,该属性赋值为 "src"
2. 根据传入的键值对,将data-cssvars值为 “src” 的内部css自定义变量替换为对应的色值
3. 替换完成,data-cssvars属性值改为 “out” ,并将其整体以style标签的形式插入到文档中
- cssVars弊端
如果css变量特别多,或者css文件特别大,那么替换的时长比较长,性能不好,用户体验也不会好,因为会有一个颜色切换的过程,过程还很明显。preserveStatic设置为false也是这个原因,将替换的样式,只涉及到颜色的部分插入到文档中,再之后替换色值的时候会稍微提高下性能。但是我试过这个配置,效果不是很理想
方案二:利用less的映射+混入 / scss的map-get+混入实现换肤
方案三:利用less在线编译实现
引入less.js。通过less.ModifyVars改变变量
这种方式不是很友好,大概的逻辑是:页面的less变量写在variable.less中,页面需要换肤的样式可以写在A.less B.less ...,在index.less中分别引入variable.less,A.less,B.less,然后在modifyVars中,修改某个less变量。
注意:less文件要放在public中,否则modifyVars方法会报错
具体的实现步骤,可以参考这两位同学的文章
补充说明:
上面两篇文章都是在vue.config.js中定义了初始化的变量。而我说的方式是初始化less文件,然后在index中引用,感觉这种方式也是能行得通的(我确实是没试过这样做)
所以综上,要换肤的话,实现起来还是比较麻烦的,而且把样式文件抽离出来放在public中,不说前期开发写的别扭,还容易有样式覆盖的可能,因为没有了scoped作为保护。再而且如果是一个已经成型的项目,用这种方式做换肤,各种样式抽离+样式覆盖简直是灾难~所以这种方式不建议使用
方案四:用less编译工具编译下新的样式文件
其实这种方式还是属于硬编码,维护另一个样式总的文件,只不过可以用less书写。编译后把样式.css文件参与换肤中,每次切换主题,link引用不同的样式文件.
这种方式前期开发,后期修改色值,都很麻烦,
以上就是关于网站换肤的集中方案的记录。当然还有支持用户自定义色值的换肤,elementUI的动态换肤,还没有看这种解决方案~
有时间的话,我也要看下微信小程序的换肤再继续记录下~