CSS变量实现主题切换方案
核心原理:CSS变量(自定义属性)允许我们在CSS中定义可复用的值,通过var()
函数引用这些值。在主题切换中,我们通过修改根元素上的data-theme
属性,让CSS选择器自动应用对应的变量值,从而实现主题的即时切换。
前置知识
css 变量
CSS变量是CSS3引入的一项功能,用来定义可重用的值,并在整个样式表中引用它们,基本语法如下
- 定义变量
css
/* 在选择器内定义变量 */
:root {
--变量名: 值;
}
变量名必须以两个连字符( -- )开头,后面跟变量名称
- 使用CSS变量
css
选择器 {
属性: var(--变量名);
}
使用 var() 函数来引用已定义的变量
特点与优势
- 可重用性 :在一处定义,多处使用,便于统一管理样式。
- 动态性 :可以通过JavaScript动态修改CSS变量的值。
- 作用域 :变量遵循CSS的级联规则,可以在不同的选择器中定义,拥有不同的作用域。
- 继承性 :子元素会继承父元素定义的CSS变量。
- 默认值 : var() 函数可以设置默认值,当变量未定义时使用: var(--变量名, 默认值) 。
CSS属性选择器
属性选择器可以根据元素的属性或属性值来选择元素。
基本语法
-
属性\] :选择所有具有指定属性的元素 ```css [title] { color: blue; } ```
css[data-theme="dark"] { /* 样式 */ }
-
属性\^="值"\] :选择属性值以指定值开头的元素 ```css [class^="btn-"] { /* 样式 */ } ```
css[src$=".jpg"] { /* 样式 */ }
-
属性\*="值"\] :选择属性值包含指定值的元素 ```css [class*="card-"] { /* 样式 */ } ```
css[class~="active"] { /* 样式 */ }
具体实现
- CSS变量定义,使用
css
/* 1. 在:root中定义全局CSS变量,:root 是CSS中的伪类选择器,它指向的是文档的 根元素 */
:root {
/* 默认主题的变量 */
--color-primary: #007bff;
--color-background: #ffffff;
--color-text: #333333;
}
/* 2. 定义不同主题的变量覆盖,当元素的data-theme属性值改变时,对应的主题选择器内的变量会覆盖:root中的默认变量 */
[data-theme="dark"] {
--color-primary: #1abfe8;
--color-background: #1f1f1f;
--color-text: #f814da;
}
/* 3. 在样式中使用CSS变量 */
body {
background-color: var(--color-background);
color: var(--color-text);
}
.button {
background-color: var(--color-primary);
color: var(--color-text);
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
- HTML结构
html
<body>
<button onclick="switchTheme('dark')">切换暗色主题</button>
<button onclick="switchTheme('light')">切换亮色主题</button>
<button class="button">这是一个主题按钮</button>
<script>
function switchTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
// 例如,切换到暗色主题
switchTheme('dark');
// 或者切换到亮色主题
switchTheme('light');
</script>
</body>
CSS变量主题切换与自定义颜色功能
核心思路
- 在
:root
中定义全局CSS
变量为默认主题的变量 - 通过类名定义不同的风格的主题变量,暗色风格(
dark-theme
),(暖色风格warm-theme
) - 当选择系统预设主题时给
body
加上主题变量的类名(dark-theme
,warm-theme
),这样类名的变量就会覆盖:root
中的默认主题变量 - 选择默认风格 移除掉
body
上的类名,用:root
中的默认变量 - 当用户自定义变量颜色时,通过
setProperty
更新:root
中对应的CSS变量
xml
#### 具体实现
``` html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CSS变量主题切换与自定义</title>
<style>
:root {
/* 定义默认主题变量 */
--primary-color: #3498db;
--secondary-color: #2ecc71;
--background-color: #f5f5f5;
--text-color: #333333;
--card-bg-color: #ffffff;
--shadow-color: rgba(0, 0, 0, 0.1);
--border-radius: 8px;
--transition-speed: 0.3s;
}
/* 暗色主题变量 */
.dark-theme {
--primary-color: #2980b9;
--secondary-color: #27ae60;
--background-color: #1a1a1a;
--text-color: #f5f5f5;
--card-bg-color: #2d2d2d;
--shadow-color: rgba(0, 0, 0, 0.3);
}
/* 暖色主题变量 */
.warm-theme {
--primary-color: #e74c3c;
--secondary-color: #f39c12;
--background-color: #fef9f3;
--text-color: #5a3921;
--card-bg-color: #ffffff;
--shadow-color: rgba(231, 76, 60, 0.2);
}
/* 基础样式 */
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: var(--background-color);
color: var(--text-color);
transition: background-color var(--transition-speed), color var(--transition-speed);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid var(--primary-color);
margin-bottom: 30px;
}
h1 {
color: var(--primary-color);
margin: 0;
}
.theme-controls {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.theme-switcher {
display: flex;
gap: 10px;
}
.theme-btn {
padding: 8px 16px;
border: none;
border-radius: var(--border-radius);
background-color: var(--primary-color);
color: white;
cursor: pointer;
transition: background-color var(--transition-speed), transform 0.1s;
}
.theme-btn:hover {
background-color: var(--secondary-color);
transform: translateY(-2px);
}
.theme-btn.active {
background-color: var(--secondary-color);
box-shadow: 0 0 0 2px var(--text-color);
}
.color-customizer {
display: flex;
gap: 15px;
align-items: center;
background-color: var(--card-bg-color);
padding: 15px;
border-radius: var(--border-radius);
box-shadow: 0 2px 4px var(--shadow-color);
}
.color-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.color-label {
font-size: 0.8rem;
color: var(--text-color);
}
.color-picker {
width: 40px;
height: 40px;
border: none;
border-radius: 50%;
cursor: pointer;
background: none;
}
.color-picker::-webkit-color-swatch {
border-radius: 50%;
border: 2px solid var(--text-color);
}
.color-picker::-moz-color-swatch {
border-radius: 50%;
border: 2px solid var(--text-color);
}
.card-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-top: 30px;
}
.card {
background-color: var(--card-bg-color);
border-radius: var(--border-radius);
padding: 20px;
box-shadow: 0 4px 6px var(--shadow-color);
transition: transform var(--transition-speed), box-shadow var(--transition-speed);
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 15px var(--shadow-color);
}
.card h3 {
color: var(--primary-color);
margin-top: 0;
}
.card p {
color: var(--text-color);
}
.feature-list {
list-style-type: none;
padding: 0;
}
.feature-list li {
padding: 8px 0;
border-bottom: 1px solid var(--shadow-color);
}
.feature-list li:before {
content: "✓";
color: var(--secondary-color);
margin-right: 10px;
font-weight: bold;
}
.stats {
display: flex;
justify-content: space-around;
margin-top: 30px;
padding: 20px;
background-color: var(--card-bg-color);
border-radius: var(--border-radius);
box-shadow: 0 4px 6px var(--shadow-color);
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: var(--primary-color);
}
.stat-label {
color: var(--text-color);
font-size: 0.9rem;
}
.code-block {
background-color: var(--card-bg-color);
border-left: 4px solid var(--primary-color);
padding: 15px;
margin: 15px 0;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
overflow-x: auto;
}
.code-block pre {
margin: 0;
font-family: 'Courier New', monospace;
color: var(--text-color);
}
@media (max-width: 768px) {
header {
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.theme-controls {
flex-direction: column;
width: 100%;
}
.color-customizer {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>CSS变量主题切换与自定义</h1>
<div class="theme-controls">
<div class="theme-switcher">
<button class="theme-btn active" data-theme="default">默认主题</button>
<button class="theme-btn" data-theme="dark">暗色主题</button>
<button class="theme-btn" data-theme="warm">暖色主题</button>
</div>
<div class="color-customizer">
<div class="color-group">
<label class="color-label">主色</label>
<input type="color" class="color-picker" id="primary-color" value="#3498db">
</div>
<div class="color-group">
<label class="color-label">辅色</label>
<input type="color" class="color-picker" id="secondary-color" value="#2ecc71">
</div>
<div class="color-group">
<label class="color-label">背景</label>
<input type="color" class="color-picker" id="background-color" value="#f5f5f5">
</div>
</div>
</div>
</header>
<main>
<h2>CSS变量主题切换与自定义功能</h2>
<p>这个示例展示了如何使用CSS变量实现动态主题切换和用户自定义颜色功能。您可以选择预设主题,也可以使用颜色选择器自定义主题颜色。</p>
<div class="card-container">
<div class="card">
<h3>CSS变量定义</h3>
<p>在:root选择器中定义默认主题的变量:</p>
<div class="code-block">
<pre>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--background-color: #f5f5f5;
--text-color: #333333;
}</pre>
</div>
</div>
<div class="card">
<h3>动态更新变量</h3>
<p>使用JavaScript动态更新CSS变量值:</p>
<div class="code-block">
<pre>
document.documentElement.style.setProperty(
'--primary-color',
newColor
);</pre>
</div>
</div>
<div class="card">
<h3>保存用户偏好</h3>
<p>将用户的自定义主题保存到localStorage:</p>
<div class="code-block">
<pre>
localStorage.setItem(
'userTheme',
JSON.stringify(themeColors)
);</pre>
</div>
</div>
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-value">3</div>
<div class="stat-label">预设主题</div>
</div>
<div class="stat-item">
<div class="stat-value">3</div>
<div class="stat-label">可自定义颜色</div>
</div>
<div class="stat-item">
<div class="stat-value">100%</div>
<div class="stat-label">纯CSS实现</div>
</div>
</div>
<div class="card">
<h3>使用说明</h3>
<ul class="feature-list">
<li>点击预设主题按钮切换整体主题风格</li>
<li>使用颜色选择器自定义主要颜色、次要颜色和背景颜色</li>
<li>自定义颜色偏好会自动保存</li>
</ul>
</div>
</main>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const themeButtons = document.querySelectorAll('.theme-btn');
const colorPickers = document.querySelectorAll('.color-picker');
// 从localStorage加载保存的主题
loadSavedTheme();
// 主题切换按钮事件
themeButtons.forEach(button => {
button.addEventListener('click', function() {
const theme = this.getAttribute('data-theme');
// 移除所有主题类
document.body.classList.remove('dark-theme', 'warm-theme');
// 添加选中的主题类
if (theme !== 'default') {
document.body.classList.add(theme + '-theme');
}
// 更新按钮激活状态
themeButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
// 更新颜色选择器值为当前主题的值
updateColorPickers();
// 保存当前主题
saveCurrentTheme();
});
});
// 颜色选择器事件
colorPickers.forEach(picker => {
picker.addEventListener('input', function() {
const colorType = this.id;
const colorValue = this.value;
// 更新对应的CSS变量
document.documentElement.style.setProperty(
`--${colorType.replace('-', '-')}`,
colorValue
);
// 保存自定义颜色
saveCustomColors();
});
});
// 更新颜色选择器值为当前主题的值
function updateColorPickers() {
const computedStyle = getComputedStyle(document.documentElement);
document.getElementById('primary-color').value =
rgbToHex(computedStyle.getPropertyValue('--primary-color').trim());
document.getElementById('secondary-color').value =
rgbToHex(computedStyle.getPropertyValue('--secondary-color').trim());
document.getElementById('background-color').value =
rgbToHex(computedStyle.getPropertyValue('--background-color').trim());
}
// 将RGB颜色转换为十六进制
function rgbToHex(rgb) {
// 如果已经是十六进制,直接返回
if (rgb.startsWith('#')) return rgb;
// 解析RGB值
const rgbValues = rgb.match(/\d+/g);
if (!rgbValues || rgbValues.length < 3) return '#000000';
const r = parseInt(rgbValues[0]).toString(16).padStart(2, '0');
const g = parseInt(rgbValues[1]).toString(16).padStart(2, '0');
const b = parseInt(rgbValues[2]).toString(16).padStart(2, '0');
return `#${r}${g}${b}`;
}
// 保存自定义颜色到localStorage
function saveCustomColors() {
const computedStyle = getComputedStyle(document.documentElement);
const customColors = {
primary: computedStyle.getPropertyValue('--primary-color').trim(),
secondary: computedStyle.getPropertyValue('--secondary-color').trim(),
background: computedStyle.getPropertyValue('--background-color').trim()
};
localStorage.setItem('customColors', JSON.stringify(customColors));
}
// 保存当前主题到localStorage
function saveCurrentTheme() {
const activeTheme = document.querySelector('.theme-btn.active').getAttribute('data-theme');
localStorage.setItem('activeTheme', activeTheme);
}
// 从localStorage加载保存的主题
function loadSavedTheme() {
// 加载保存的主题
const savedTheme = localStorage.getItem('activeTheme');
if (savedTheme) {
// 移除所有主题类
document.body.classList.remove('dark-theme', 'warm-theme');
// 添加保存的主题类
if (savedTheme !== 'default') {
document.body.classList.add(savedTheme + '-theme');
}
// 更新按钮激活状态
themeButtons.forEach(btn => {
btn.classList.remove('active');
if (btn.getAttribute('data-theme') === savedTheme) {
btn.classList.add('active');
}
});
}
// 加载自定义颜色
const savedColors = localStorage.getItem('customColors');
if (savedColors) {
const customColors = JSON.parse(savedColors);
// 应用自定义颜色
document.documentElement.style.setProperty('--primary-color', customColors.primary);
document.documentElement.style.setProperty('--secondary-color', customColors.secondary);
document.documentElement.style.setProperty('--background-color', customColors.background);
}
// 更新颜色选择器
updateColorPickers();
}
});
</script>
</body>
</html>