js及vue主题切换方案

一、js切换主题方案

1、link动态切换(不推荐)

  • 通过改变link 标签的 href 属性实现动态修改样式
  • 优点:实现了按需加载,提高了性能。
  • 缺点:动态加载样式文件,可能会因为网络问题导致样式加载过慢;可维护性较差,后续新增或修改主题较为麻烦。
  • 示例仅为核心切换部分,实际可根据业务拆分至全局文件,自行调配
xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
	<meta charset="UTF-8">
	<title>主题切换示例</title>
	<!--
		默认主题
		id="theme-style" - 为link元素设置ID
		href指向默认主题CSS文件
	-->
	<link id="theme-style" rel="stylesheet" href="./assets/css/default.css">
</head>
<body>
<div class="container">
	<h1>主题切换演示</h1>
	<!--显示当前主题名称-->
	<p>当前主题: <span id="current-theme">默认</span></p>
	<!-- 主题切换按钮组 -->
	<div class="theme-buttons">
		<!--
				每个按钮通过onclick事件调用changeTheme函数
				并传入对应的主题名称参数
		-->
		<button onclick="changeTheme('default')">默认</button>
		<button onclick="changeTheme('black')">暗黑</button>
		<button onclick="changeTheme('greenyellow')">淡绿</button>
		<button onclick="changeTheme('aqua')">浅蓝</button>
	</div>
</div>

<script>
	// 主题切换
	function changeTheme(themeName) {
		// 获取当前的theme-style链接元素
		const themeStyle = document.getElementById('theme-style');
		
		// 根据选择主题更新CSS文件路径
		themeStyle.href = `./assets/css/${ themeName }.css`;
		
		// 更新显示的当前主题名称
		document.getElementById('current-theme').textContent = getThemeDisplayName(themeName);
		
		// 将用户选择保存到localStorage,实现主题持久化
		localStorage.setItem('theme', themeName);
	}
	
	// 获取主题的显示名称
	function getThemeDisplayName(themeName) {
		const themeNames = {
			'aqua': '浅蓝',
			'black': '暗黑',
			'default': '默认',
			'greenyellow': '淡绿'
		};
		return themeNames[themeName] || themeName; // 如果没有匹配则返回原名称
	}
	
	// 初始化主题
	function initTheme() {
		// 获取历史主题或使用默认主题
		const savedTheme = localStorage.getItem('theme') || 'default';
		
		// 调用主题切换函数更新主题
		changeTheme(savedTheme);
	}
	
	// 页面加载时初始化主题
	initTheme();
</script>

2、可参考vue切换方案6(推荐)

3、css变量+自定义属性切换(推荐)

  • 优点:不会因为网络问题导致样式切换延迟;使用方便;新增或修改主题方便灵活
  • 缺点:首屏加载时会略微耗时
  • 示例仅为核心切换部分,实际可根据业务拆分至全局文件,自行调配
xml 复制代码
<!DOCTYPE html>
<!--html的data-theme属性控制全局主题-->
<html lang="en" data-theme="default">
<head>
	<meta charset="UTF-8">
	<title>主题切换</title>
	<style>
		/* 默认主题变量 */
		:root {
			--bg-color: #ffffff;
			--text-color: #333333;
			transition: background-color 0.3s ease, color 0.3s ease;
		}
		/* 暗黑主题变量 */
		[data-theme="black"] {
			--bg-color: #333333;
			--text-color: #ffffff;
		}
		/* 浅蓝主题变量 */
		[data-theme="aqua"] {
			--bg-color: aqua;
			--text-color: greenyellow;
		}
		/* 淡绿主题变量 */
		[data-theme="greenyellow"] {
			--bg-color: greenyellow;
			--text-color: aqua;
		}
		/* 页面内使用 */
		body {
			background-color: var(--bg-color);
			color: var(--text-color);
		}
		#current-theme {
			color: var(--text-color);
		}
		h3 {
			color: var(--text-color);
		}
		
		/* 仅按钮样式 可忽略 */
		.default {
			color: #333333;
			background-color: #ffffff;
			border-color: #ffffff;
		}
		.black {
			color: #ffffff;
			background-color: #333333;
			border-color: #333333;
		}
		.aqua {
			color: greenyellow;
			background-color: aqua;
			border-color: aqua;
		}
		.greenyellow {
			color: aqua;
			background-color: greenyellow;
			border-color: greenyellow;
		}
	</style>
</head>
<body>
<div class="app">
	<h3>主题切换演示</h3>
	<div class="theme-Text">当前主题: <span id="current-theme">默认</span></div>
	<div class="btns">
		<!--data-theme-name为自定义属性,用于存储主题名称-->
		<button class="btn default" data-theme-name="default">默认</button>
		<button class="btn black" data-theme-name="black">暗黑</button>
		<button class="btn greenyellow" data-theme-name="greenyellow">淡绿</button>
		<button class="btn aqua" data-theme-name="aqua">浅蓝</button>
	</div>
</div>
<script>
	// 获取包含所有主题按钮的容器元素
	const btns = document.querySelector('.btns');
	
	// 主题切换
	function themeToggle(theme) {
		let html = document.querySelector('html');
		// 设置data-theme属性值
		html.setAttribute('data-theme', theme);
		// 保存当前主题
		localStorage.setItem('theme', theme);
		// 更新页面显示的当前主题名称
		document.getElementById('current-theme').textContent = getThemeDisplayName(theme);
	}
	
	// 获取主题的显示名称
	function getThemeDisplayName(theme) {
		// 主题名称映射表
		const themeNames = {
			'aqua': '浅蓝',
			'black': '暗黑',
			'default': '默认',
			'greenyellow': '淡绿'
		};
		// 返回对应的中文名称,如果没有匹配则返回原值
		return themeNames[theme] || theme;
	}
	
	// 按钮事件
	btns.addEventListener('click', (e) => {
		// 检查点击的是否是带有'btn'类的元素
		if(e.target.classList.contains('btn')) {
			// 从被点击按钮的dataset中获取data-theme-name属性值
			const themeName = e.target.dataset.themeName;
			// 调用主题切换函数
			themeToggle(themeName);
		}
	});
	
	// 初始化主题
	function initTheme() {
		// 尝试从本地存储获取之前保存的主题,如果没有则使用'default'默认主题
		const savedTheme = localStorage.getItem('theme') || 'default';
		// 应用获取到的主题
		themeToggle(savedTheme);
	}
	
	// 页面加载时初始化主题
	initTheme();
</script>
</body>
</html>

二、vue切换主题方案

1、css变量+动态类名/自定义属性

  • 使用方法参考js切换方法3,定义各个主体全局变量,
  • 在index.html 中,定义data-theme 属性和默认主题值 default
  • App.vue 中,初始化主题
  • 项目任务需要切换的位置使用css变量
  • 在任意位置定义变更自定义属性的逻辑,实现全局切换

2、css的filter(不推荐)

  • css的filter过滤实现全局切换
  • 无法修改局部变量,不太友好

3、v-bind切换(不推荐)

  • 性能不确定,不建议用

4、link文件或顶级css类名切换(不推荐)

  • 新增或修改主题较为麻烦比较繁琐

5、组件库的主题切换

  • 可通过修改当前组件库的全局变量,达到换肤效果
  • 如element-puls,可以修改组件库全局变量,使默认色切换
  • 下面例子演示将element主题色#409eff变为粉色#ffc0cb
ini 复制代码
<script>
// 处理主题样式
function handleThemeStyle(theme) {
  document.documentElement.style.setProperty('--el-color-primary', theme);
  for(let i = 1; i <= 9; i++) {
    document.documentElement.style.setProperty(`--el-color-primary-light-${ i }`, `${ getLightColor(theme, i / 10) }`);
  }
  for(let i = 1; i <= 9; i++) {
    document.documentElement.style.setProperty(`--el-color-primary-dark-${ i }`, `${ getDarkColor(theme, i / 10) }`);
  }
}

// hex颜色转rgb颜色
function hexToRgb(str) {
  const newStr = str.replace('#', '');
  let hexs = newStr.match(/../g);
  let newHexs = [];
  if(hexs) {
    for(let i = 0; i < 3; i++) {
      newHexs.push(parseInt(hexs[i], 16));
    }
  }
  return newHexs;
}

// rgb颜色转Hex颜色
function rgbToHex(r, g, b) {
  let hexs = [r.toString(16), g.toString(16), b.toString(16)];
  for(let i = 0; i < 3; i++) {
    if(hexs[i].length == 1) {
      hexs[i] = `0${ hexs[i] }`;
    }
  }
  return `#${ hexs.join('') }`;
}

// 变浅颜色值
function getLightColor(color, level) {
  let rgb = hexToRgb(color);
  for(let i = 0; i < 3; i++) {
    rgb[i] = Math.floor((255 - rgb[i]) * level + rgb[i]);
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2]);
}

// 变深颜色值
function getDarkColor(color, level) {
  let rgb = hexToRgb(color);
  for(let i = 0; i < 3; i++) {
    rgb[i] = Math.floor(rgb[i] * (1 - level));
  }
  return rgbToHex(rgb[0], rgb[1], rgb[2]);
}
handleThemeStyle('#ffc0cb')
</script>

6、js修改css变量换肤方案

  • 为了方便演示,代码写在一个组件里,一般情况在入口文件中定义初始的全局变量;
  • js部分可拆解为通用方法
  • 可以组合动画效果,切换更流畅
  • 切换部分可封装为系统主题切换组件,可以通过vuex或者pinia控制存储与切换
  • 完整代码
xml 复制代码
<template>
  <div class="box">
    <h3>系统主题选择</h3>
    <div>当前主题:<span style="color:var(--text-color)">{{ currentTheme }}</span></div>
    <div v-for="(item,index) in themeList" :key="index" :style="{'background-color':item.textColor}" class="theme-item"
         @click="changeTheme(item)">{{ item.title }}
    </div>
    <button @click="resetTheme">重置主题</button>
  </div>
</template>
<script setup>
import { ref } from 'vue';

const currentTheme = ref(null);
const themeList = [
  { title: '科技蓝', textColor: '#165dff', bgColor: '#ffffff', borderColor: '#cccccc' },
  { title: '静谧绿', textColor: '#18a058', bgColor: '#ffffff', borderColor: '#cccccc' },
  { title: '星辰紫', textColor: '#8a2be2', bgColor: '#ffffff', borderColor: '#cccccc' },
  { title: '曜影红', textColor: '#a41f24', bgColor: '#ffffff', borderColor: '#cccccc' }
];

// 设置主题
function setTheme(themeObj) {
  document.documentElement.style.setProperty('--text-color', themeObj.textColor);
  document.documentElement.style.setProperty('--bg-color', themeObj.bgColor);
  document.documentElement.style.setProperty('--border-color', themeObj.borderColor);
}

// 系统主题切换
function changeTheme(themeObj) {
  currentTheme.value = themeObj.title;
  setTheme(themeObj);
  localStorage.setItem('theme', JSON.stringify(themeObj));
}

// 重置主题
function resetTheme() {
  setTheme(themeList[0]);
  localStorage.removeItem('theme');
}

// 初始化主题
function initTheme() {
  // 本地读取保存的主题
  const savedTheme = JSON.parse(localStorage.getItem('theme'));
  // 使用历史主题或默认主题
  if(savedTheme?.title) {
    changeTheme(savedTheme);
    currentTheme.value = savedTheme.title;
  }
  else {
    changeTheme(themeList[0]);
    currentTheme.value = themeList[0].title;
  }
};
initTheme();
</script>
<style lang="scss">
:root {
  --text-color: #165dff;
  --bg-color: #f5f7fa;
  --border-color: #e4e7ed;
}
.box {
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: var(--bg-color);
  .theme-item {
    width: 200px;
    color: #ffffff;
    padding: 10px 40px;
    margin: 10px;
    text-align: center;
    cursor: pointer;
    transition: all 1s;
    &:hover {
      opacity: 0.8;
    }
  }
}
</style>
相关推荐
蓝婷儿5 分钟前
每天一个前端小知识 Day 27 - WebGL / WebGPU 数据可视化引擎设计与实践
前端·信息可视化·webgl
然我11 分钟前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法
OpenTiny社区31 分钟前
告别代码焦虑,单元测试让你代码自信力一路飙升!
前端·github
kk_stoper39 分钟前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源
pe7er41 分钟前
HTTPS:本地开发绕不开的设置指南
前端
晨枫阳1 小时前
前端VUE项目-day1
前端·javascript·vue.js
江山如画,佳人北望1 小时前
SLAM 前端
前端
患得患失9491 小时前
【前端】【Iconify图标库】【vben3】createIconifyIcon 实现图标组件的自动封装
前端
颜酱1 小时前
抽离ant-design后台的公共查询设置
前端·javascript·ant design
用户95251151401551 小时前
js最简单的解密分析
前端