纯 css 实现前端主题切换+自定义方案

CSS变量实现主题切换方案

核心原理:CSS变量(自定义属性)允许我们在CSS中定义可复用的值,通过var()函数引用这些值。在主题切换中,我们通过修改根元素上的data-theme属性,让CSS选择器自动应用对应的变量值,从而实现主题的即时切换。

前置知识

css 变量

CSS变量是CSS3引入的一项功能,用来定义可重用的值,并在整个样式表中引用它们,基本语法如下

  1. 定义变量
css 复制代码
/* 在选择器内定义变量 */
:root {
  --变量名: 值;
}

变量名必须以两个连字符( -- )开头,后面跟变量名称

  1. 使用CSS变量
css 复制代码
选择器 {
  属性: var(--变量名);
}

使用 var() 函数来引用已定义的变量

特点与优势

  1. 可重用性 :在一处定义,多处使用,便于统一管理样式。
  2. 动态性 :可以通过JavaScript动态修改CSS变量的值。
  3. 作用域 :变量遵循CSS的级联规则,可以在不同的选择器中定义,拥有不同的作用域。
  4. 继承性 :子元素会继承父元素定义的CSS变量。
  5. 默认值 : var() 函数可以设置默认值,当变量未定义时使用: var(--变量名, 默认值) 。

CSS属性选择器

属性选择器可以根据元素的属性或属性值来选择元素。

基本语法

  1. 属性\] :选择所有具有指定属性的元素 ```css [title] { color: blue; } ```

    css 复制代码
    [data-theme="dark"] { /* 样式 */ }
  2. 属性\^="值"\] :选择属性值以指定值开头的元素 ```css [class^="btn-"] { /* 样式 */ } ```

    css 复制代码
    [src$=".jpg"] { /* 样式 */ }
  3. 属性\*="值"\] :选择属性值包含指定值的元素 ```css [class*="card-"] { /* 样式 */ } ```

    css 复制代码
    [class~="active"] { /* 样式 */ }

具体实现

  1. 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;
}
  1. 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变量主题切换与自定义颜色功能

核心思路

  1. :root 中定义全局 CSS 变量为默认主题的变量
  2. 通过类名定义不同的风格的主题变量,暗色风格(dark-theme),(暖色风格warm-theme
  3. 当选择系统预设主题时给 body 加上主题变量的类名(dark-themewarm-theme),这样类名的变量就会覆盖 :root 中的默认主题变量
  4. 选择默认风格 移除掉body 上的类名,用 :root 中的默认变量
  5. 当用户自定义变量颜色时,通过 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>
相关推荐
Zuckjet_3 小时前
第 7 篇:交互的乐趣 - 响应用户输入
前端·javascript·webgl
我总是词不达意3 小时前
vue3 + el-upload组件集成阿里云视频点播从本地上传至点播存储
前端·vue.js·阿里云·elementui
用户481178812873 小时前
求大佬解惑:高度与宽度百分比设置问题
前端
anyup3 小时前
🔥开源零配置!10 分钟上手:create-uni + uView Pro 快速搭建企业级 uni-app 项目
前端·前端框架·uni-app
帆张芳显3 小时前
智表 ZCELL 公式引擎,帮你解锁自定义函数与跨表计算的强大能力
前端·javascript
北城以北88884 小时前
Vue-- Axios 交互(一)
前端·javascript·vue.js
shelutai4 小时前
实现提供了完整的 Flutter Web 文件上传解决方案
前端·flutter
im_AMBER4 小时前
Web 开发 29
前端·学习·web
前端开发爱好者4 小时前
Vite➕ 收费了!
前端·javascript·vue.js