字数
1217 字
阅读时间
6 分钟
通常内容网站一般都带有 黑夜/白天 主题切换的功能,这里大致罗列下一些常见的主题切换方案并分析其优劣
link 标签动态引入
通过写几套单独的样式文件,通过 link 标签 进行选择性加载,或者选择性的改变 link 标签的 href 属性
优点:
- 实现了按需加载,提高了首屏响应速度
缺点: - 如果样式文件过大又网络不好,切换主题时会存在加载延迟
- 各个主题样式是写死的,后续针对某一主题样式表修改或者新增主题也很麻烦
提前引入所有主题文件,做样式覆盖
js
/* day样式主题 */
body.day .box {
color: #f90;
background: #fff;
}
/* dark样式主题 */
body.dark .box {
color: #eee;
background: #333;
}
.box {
width: 100px;
height: 100px;
border: 1px solid #000;
}
// js事件
function change(theme) {
document.body.className = theme;
}
<div class="box">
<p>hello</p>
</div>
<p>
选择样式:
<button onclick="change('day')">day</button>
<button onclick="change('dark')">dark</button>
</p>优点:
- 主题切换时不用额外加载多余样式文件,响应快
缺点: - 首屏加载速度慢
- 新增主题或者修改某一样式主题很麻烦
CSS 变量 + 类名切换
依然是提前将样式文件载入,切换时将指定的根元素类名更换。不过这里相对灵活的是,默认在根作用域下定义好 CSS 变量,只需要在不同的主题下更改 CSS 变量对应的取值即可
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
:root {
--bg: #000;
--fontSize: 25px;
}
.pink-theme {
--bg: hotpink;
}
body {
transition: background 1s;
background: var(--bg);
}
button {
position: fixed;
top: 50%;
left: 50%;
transition: color 1s;
transform: translate(-50%, -50%);
padding: 20px;
border: none;
background: #fff;
font-size: var(--fontSize);
color: var(--bg);
}
</style>
</head>
<body>
<button>点击切换</button>
<script>
document.querySelector("button").addEventListener("click", () => {
if (document.body.classList.contains("pink-theme")) {
document.body.classList.remove("pink-theme");
} else {
document.body.classList.add("pink-theme");
}
});
</script>
</body>
</html>优点:
- 主题切换响应快
- 新增主题或者修改某一主题样式方便
缺点: - 首屏加载相对较慢
CSS 变量 + setProperty
只需在全局中设置好预设的全局 CSS 变量样式,无需单独为每一个主题类名下重新设定 CSS 变量值,因为主题是由用户动态决定
javascript
:root {
--theme-color: #333;
--theme-background: #eee;
}
export const setCssVar = (prop: string, val: any, dom = document.documentElement) => {
dom.style.setProperty(prop, val)
}
setCssVar('--theme-color', color)vue3 的 v-bind
虽然这种方式存在局限性只能在 Vue 开发中使用,但是为 Vue 项目开发者做动态样式更改提供了又一个不错的方案
原理:给元素绑定 CSS 变量,在绑定的数据更新时调用 CSSStyleDeclaration.setProperty 更新 CSS 变量值
javascript
<script setup>
// 这里可以是原始对象值,也可以是ref()或reactive()包裹的值,根据具体需求而定
const theme = {
color: 'red'
}
</script>
<template>
<p>hello</p>
</template>
<style scoped>
p {
color: v-bind('theme.color');
}
</style>优点:
- 主题切换响应快
- 新增主题或者修改某一主题样式方便
缺点: - 首屏加载相对较慢
- 这种方式只要是在组件上绑定了动态样式的地方都会有对应的编译成
哈希化的 CSS 变量,而不像方案 3 统一地就在: root 上设置(不确定在达到一定量级以后的性能),也可能正是如此,Vue 官方也并未采用此方式做全站的主题切换
stylus 等 css 预处理器的变量
通过内置的语法,如变量名和 mixin 来实现,本质上还是【提前引入所有主题文件,做样式覆盖】这种方式
变量文件:
stylus
standard = {
name: "standard", //标识主题
background_color_main : #ffffff, // 主要背景色
background_color_main_opacity : #ffffff
}
bhzq = {
name: "bhzq",
background_color_main : #238BCB, // 主要背景色
background_color_main_opacity : #238BCB4d
}
themeList = (standard bhzq);mixin 文件:
css
@import "./themes.styl";//引入配置
//获取背景色
bg_color($color)
//默认样式(任意选择一个主题即可注意要和下面的默认主题一致)
background-color: standard[$color];
for item in themeList
//判断html的data-theme的属性值,{}是Stylus插值
[data-theme = {item.name}] & {
//这里的item可以当作一个对象,item[$color]可表示为:对象名.属性
background-color: item[$color];
}
//获取字体颜色
font_color($color)
color: standard[$color];
for item in themeList
[data-theme = {item.name}] & {
color: item[$color];
}
// 获取边框颜色
border_color($width $type $color)
color: standard[$color];
for item in themeList
[data-theme = {item.name}] & {
border: $width $type item[$color];
}总结
综合下来还是 【CSS 变量 +setProperty】实现最佳