前端实现主题定制功能
1.link标签动态引入
1.1 思路
其做法就是提前准备好几套CSS主题样式文件,在需要的时候,创建link标签动态加载到head标签中,或者是动态改变link标签的href属性。
界面如下:
网络请求如下:
1.2 代码实现
创建light-theme.css和dark-theme.css:
/* light-theme.css */
body {
background-color: #ffffff;
color: #333333;
}
button {
background-color: #3498db;
color: #ffffff;
}
/* dark-theme.css */
body {
background-color: #2c3e50;
color: #ecf0f1;
}
button {
background-color: #e74c3c;
color: #ffffff;
}
通过JavaScript动态加载不同的CSS文件:
function loadTheme(theme) {
const head = document.head;
let link = document.getElementById('theme-link');
if (link) {
link.href = theme;
} else {
link = document.createElement('link');
link.id = 'theme-link';
link.rel = 'stylesheet';
link.href = theme;
head.appendChild(link);
}
}
// 监听主题切换按钮
document.getElementById('theme-switch').addEventListener('click', () => {
const currentTheme = document.getElementById('theme-link').href.includes('light-theme.css') ? 'dark-theme.css' : 'light-theme.css';
loadTheme(currentTheme);
});
1.3 优缺点分析
- 优点:
- 实现简单:只需切换样式文件,不需要复杂的逻辑。
- 适应性广:适用于所有前端框架和纯HTML项目。
- 缺点:
- 性能开销:每次切换都需要重新加载CSS文件,可能导致页面闪烁。
- 维护成本:需要维护多套完整的CSS文件,代码重复度高。
2.使用CSS变量
关于css变量的知识可以看我写的博客:https://blog.csdn.net/fageaaa/article/details/146512864
2.1 代码实现
在CSS文件的:root选择器中定义全局变量,例如:
:root {
--primary-color: #3498db;
--background-color: #ffffff;
--text-color: #333333;
}
在CSS中使用这些变量:
body {
background-color: var(--background-color);
color: var(--text-color);
}
button {
background-color: var(--primary-color);
color: #ffffff;
}
通过JavaScript动态更改CSS变量的值:
function setTheme(theme) {
const root = document.documentElement;
if (theme === 'dark') {
root.style.setProperty('--primary-color', '#e74c3c');
root.style.setProperty('--background-color', '#2c3e50');
root.style.setProperty('--text-color', '#ecf0f1');
} else {
root.style.setProperty('--primary-color', '#3498db');
root.style.setProperty('--background-color', '#ffffff');
root.style.setProperty('--text-color', '#333333');
}
}
// 监听主题切换按钮
document.getElementById('theme-switch').addEventListener('click', () => {
const currentTheme = document.documentElement.style.getPropertyValue('--background-color') === '#ffffff' ? 'dark' : 'light';
setTheme(currentTheme);
});
2.2 优缺点分析
-
优点:
- 简单易用:CSS变量使用和更改都很方便。
- 性能高效:只需更改变量值,无需重新加载样式表。
- 兼容性好:适用于各种CSS预处理器,如Sass、Less等。
-
缺点:
- 浏览器兼容性:旧版浏览器(如IE)不支持CSS变量。
- 维护成本:对于大型项目,需要维护大量的变量,可能导致变量命名冲突和管理困难。
3.使用CSS预处理器实现主题切换
3.1 思路
CSS预处理器(如Sass、Less)提供了变量和混入(mixin)功能,方便实现主题切换。
3.2 代码实现
以Sass为例:
定义变量:
// variables.scss
$primary-color: #3498db;
$background-color: #ffffff;
$text-color: #333333;
$dark-primary-color: #e74c3c;
$dark-background-color: #2c3e50;
$dark-text-color: #ecf0f1;
创建主题混入:
// mixins.scss
@mixin theme($primary-color, $background-color, $text-color) {
body {
background-color: $background-color;
color: $text-color;
}
button {
background-color: $primary-color;
color: #ffffff;
}
}
应用主题:
// styles.scss
@import 'variables';
@import 'mixins';
@include theme($primary-color, $background-color, $text-color);
body.dark-theme {
@include theme($dark-primary-color, $dark-background-color, $dark-text-color);
}
通过JavaScript切换主题类名:
document.getElementById('theme-switch').addEventListener('click', () => {
document.body.classList.toggle('dark-theme');
});
3.3 优缺点分析
- 优点:
- 功能强大:支持变量、混入等高级功能,代码复用性高。
- 编译时处理:样式在编译时生成,不影响运行时性能。
- 缺点:
- 依赖编译:需要预处理器编译工具链,增加了构建复杂度。
- 灵活性低:相比CSS变量,不能在运行时动态更改变量值。
4.其它思路
4.1 Vue项目中的动态style
不多说。
4.2 Vue3中的新特性(v-bind)
虽然这种方式存在局限性只能在Vue开发中使用,但是为Vue项目开发者做动态样式更改提供了又一个不错的方案。
<script setup>
// 这里可以是原始对象值,也可以是ref()或reactive()包裹的值,根据具体需求而定
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>
Vue3中在style样式通过v-bind()
绑定变量的原理其实就是给元素绑定CSS变量,在绑定的数据更新时调用CSSStyleDeclaration.setProperty
更新CSS变量值。
我们可以利用Vuex或Pinia对全局样式变量做统一管理,如果不想使用类似的插件也可以自行封装一个hook,大致如下:
// 定义暗黑主题变量
export default {
fontSize: '16px',
fontColor: '#eee',
background: '#333',
};
// 定义白天主题变量
export default {
fontSize: '20px',
fontColor: '#f90',
background: '#eee',
};
import {
shallowRef } from 'vue';
// 引入主题
import theme_day from './theme_day';
import theme_dark from './theme_dark';
// 定义在全局的样式变量
const theme = shallowRef({
});
export function useTheme() {
// 尝试从本地读取
const localTheme = localStorage.getItem('theme');
theme.value = localTheme ? JSON.parse(localTheme) : theme_day;
const setDayTheme = () => {
theme.value = theme_day;
};
const setDarkTheme = () => {
theme.value = theme_dark;
};
return {
theme,
setDayTheme,
setDarkTheme,
};
}
使用自己封装的主题hook:
<script setup lang="ts">
import {
useTheme } from './useTheme.ts';
import MyButton from './components/MyButton.vue';
const {
theme } = useTheme();
</script>
<template>
<div class="box">
<span>Hello</span>
</div>
<my-button />
</template>
<style lang="scss">
.box {
width: 100px;
height: 100px;
background: v-bind('theme.background');
color: v-bind('theme.fontColor');
font-size: v-bind('theme.fontSize');
}
</style>
<script setup lang="ts">
import {
useTheme } from '../useTheme.ts';
const {
theme, setDarkTheme, setDayTheme } = useTheme();
const change1 = () => {
setDarkTheme();
};
const change2 = () => {
setDayTheme();
};
</script>
<template>
<button class="my-btn" @click="change1">dark</button>
<button class="my-btn" @click="change2">day</button>
</template>
<style scoped lang="scss">
.my-btn {
color: v-bind('theme.fontColor');
background: v-bind('theme.background');
}
</style>
其实从这里可以看到,跟Vue的响应式原理一样,只要数据发生改变,Vue就会把绑定了变量的地方通通更新。
- 优点:
- 不用重新加载样式文件,在样式切换时不会有卡顿
- 在需要切换主题的地方利用v-bind绑定变量即可,不存在优先级问题
- 新增或修改主题方便灵活,仅需新增或修改JS变量即可,在v-bind()绑定样式变量的地方就会自动更换
- 缺点:
- IE兼容性(忽略不计)
- 首屏加载时会牺牲一些时间加载样式资源
- 这种方式只要是在组件上绑定了动态样式的地方都会有对应的编译成哈希化的CSS变量,而不像方案3统一地就在:root上设置(不确定在达到一定量级以后的性能),也可能正是如此,Vue官方也并未采用此方式做全站的主题切换。
4.3 部分组件库自带主题实现功能
拿Element Plus为例。Element Plus使用Sass作为样式预处理器,通过配置Sass变量实现主题定制。
在项目中创建一个新的Sass文件(如element-variables.scss),并覆盖默认变量:
// element-variables.scss
$--color-primary: #1DA57A;
$--button-padding-horizontal: 20px;
在main.js中引入该文件:
import {
createApp } from 'vue';
import App from './App.vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import './styles/element-variables.scss';
createApp(App).use(ElementPlus).mount('#app');
- 优点:
- 高度可定制:通过修改预处理器变量可以灵活定制主题。
- 开发体验好:官方提供详细文档和示例,便于开发者上手。
- 缺点:
- 配置复杂:需要了解预处理器的配置和变量。
- 构建依赖:需要在构建过程中处理预处理器文件。