前言
有时候一个项目的主题并不能满足所有人的审美, 所以这个时候就需要换肤功能登场了。
下面是一个换肤demo
, 思路很简单,定义一个全局css变量
,然后在页面根元素获取变量并动态修改这个变量值即可完成。
效果
具体实现
1.准备项目
准备一个含有less
、vuex
的项目
2.安装插件
yarn add style-resources-loader vue-cli-plugin-style-resources-loader -D
3.新建global.less
global.less
用于定义全局变量设置全局默认样式
- 路径:
src/theme/global.less
(先建一个theme目录)
// 默认主题 因为会放在rgba()中 所以只需要rgb这三个值 使用rgba的好处是一个主题可以根据透明度配置更多相似主题的颜色
@themeColor: var(--themeColor, 100, 149, 237);
4.配置vue.config.js
vue.config.js
是项目可选的配置文件
- 路径: 与
package.json
同级
const path = require("path");
module.exports = {
pluginOptions: {
"style-resources-loader": {
preProcessor: "less",
patterns: [
path.resolve(__dirname, "./src/theme/global.less"),
],
},
},
};
这里通过配置style-resources-loader
让global.less
设置的变量供项目中其他文件直接使用,而避免重复在每个样式文件中通过@import
导入
5.新建model.js
model.js
用于预设几套固定主题
- 路径:
src/theme/model.js
export const themes = {
/* 默认主题 */
default: {
backgroundColor: `${100},${149},${237}`
},
/* 暗黑主题 */
dark: {
backgroundColor: `${0},${0},${0}`
},
/* 鲜红主题 */
red: {
backgroundColor: `${247},${72},${72}`
},
/* 草绿主题 */
green: {
backgroundColor: `${59},${235},${115}`
}
};
这里我预设了四套固定主题
6.新建changeTheme.js
changeTheme.js
用于定义修改主题的方法,修改样式与接收值来确定使用哪套主题的方法
- 路径:
src/theme/changeTheme.js
import {
themes
} from "./model";
// 修改样式
const changeStyle = (obj) => {
document.documentElement.style.setProperty(`--themeColor`, obj.backgroundColor);
};
// 改变主题的方法
export const setTheme = (themeName) => {
const haveTheme = themes[themeName];
// 有预设的主题名称
if (haveTheme) {
localStorage.setItem("primaryColor", haveTheme.backgroundColor);
changeStyle(haveTheme);
} else {
let haveTheme = {
backgroundColor: localStorage.getItem("primaryColor")
};
changeStyle(haveTheme);
}
};
7.动态换肤功能实现
- 路径:
src/view/Home.vue
<template>
<div class="home">
<header>温情key</header>
<main>
<div class="box-item">
<div class="title">css动画-animation</div>
<div class="content">
steps()可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,也就是指定每个阶段分为几步来展示动画
,然后根据第二个参数来决定显示效果。第二个参数设置后
其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果。
</div>
</div>
<div class="box-item box2">
<div class="title">css动画-animation</div>
<div class="content">
steps()可以传入两个参数,第一个是一个大于0的整数,他是将间隔动画等分成指定数目的小间隔动画,也就是指定每个阶段分为几步来展示动画
,然后根据第二个参数来决定显示效果。第二个参数设置后
其实和step-start,step-end同义,在分成的小间隔动画中判断显示效果。
</div>
</div>
</main>
<footer><a href="https://www.wenqingkey.cn" style="text-decoration: none;color: #fff;">www.wenqingkey.cn</a></footer>
<div class="changeTheme" :style="{ right: openThemeCom === true ? '0' : '-310px' }">
<div @click="chooseTheme">{{ openThemeCom === true ? "→" : "←" }}</div>
<div @click="setThemeHandle('default')"></div>
<div @click="setThemeHandle('dark')"></div>
<div @click="setThemeHandle('red')"></div>
<div @click="setThemeHandle('green')"></div>
<div>
<input type="color" :value="defaultColor" @input="chooseColor"/>
</div>
</div>
</div>
</template>
<script>
import { setTheme } from "../theme/changeTheme";
export default {
data() {
return {
// 选择主题组件开闭
openThemeCom: false,
};
},
computed: {
// 取色器 默认颜色
defaultColor() {
/* 将 rgb 转换为 hex 值 */
let color = localStorage.getItem("primaryColor").split(',');
let hex = "#" + ((1 << 24) + (Number(color[0]) << 16) + (Number(color[1]) << 8) + Number(color[2])).toString(16).slice(1);
// console.log(hex);
return hex;
}
},
created() {
this.initTheme();
},
methods: {
/* 初始化主题 */
initTheme() {
setTheme();
},
/* 选择主题组件显示/隐藏 */
chooseTheme() {
this.openThemeCom = !this.openThemeCom;
},
/* 切换主题 */
setThemeHandle(val) {
setTheme(val);
},
/* 自定义主题 */
chooseColor(val) {
// hex 转换为 rgb
let hex = val.target.value;
let r = parseInt('0x' + hex.slice(1, 3));
let g = parseInt('0x' + hex.slice(3, 5));
let b = parseInt('0x' + hex.slice(5, 7));
let newPrimaryColor = `${r},${g},${b}`;
// console.log(newPrimaryColor);
localStorage.setItem("primaryColor", newPrimaryColor);
setTheme();
},
},
};
</script>
<style lang="less" scoped>
.home {
widows: 100vw;
height: 100vh;
display: grid;
grid-template-rows: 50px 1fr 50px;
background: #eee;
header,
footer {
width: 100vw;
height: 50px;
background: rgba(@themeColor, 1);
text-align: center;
line-height: 50px;
font-size: 1.6em;
color: #fff;
}
main {
padding: 10px;
.box-item {
border: 1px solid #555;
}
.box2 {
margin-top: 20px;
}
.title {
padding: 6px;
color: rgba(@themeColor, 1);
font-size: 1.3em;
border-bottom: 1px solid #555;
}
.content {
padding: 10px;
background: rgba(@themeColor, 0.2);
}
}
.changeTheme {
position: fixed;
bottom: 70px;
right: 0;
height: 40px;
width: 360px;
background: #fff;
display: grid;
grid-template-columns: repeat(6, 1fr);
column-gap: 5px;
transition: 0.3s ease;
div {
position: relative;
}
div::after {
display: inline-block;
width: 60px;
height: 20px;
text-align: center;
line-height: 20px;
position: absolute;
top: -22px;
left: 0;
}
div:nth-child(1) {
line-height: 40px;
text-align: center;
}
div:nth-child(2) {
background: rgb(100, 149, 237);
}
div:nth-child(2)::after {
content: "默认";
}
div:nth-child(3) {
background: rgb(0, 0, 0);
}
div:nth-child(3)::after {
content: "暗黑";
}
div:nth-child(4) {
background: rgb(247, 72, 72);
}
div:nth-child(4)::after {
content: "鲜红";
}
div:nth-child(5) {
background: rgb(59, 235, 115);
}
div:nth-child(5)::after {
content: "草绿";
}
div:nth-child(6)::after {
content: "自定义";
}
}
}
</style>