• 基于css变量轻松实现网站的主题切换功能


    我们经常看到一些网站都有主题切换,例如vue官方文档。那他是怎么实现的呢?

    在这里插入图片描述

    检查元素,发现点击切换时,html元素会动态的添加和移除一个class:dark,然后页面主题色就变了。仔细想想,这要是放在以前,可能要写两套样式,就像这样:

    body {
      background-color: '#fff';
    }
    body.dark {
      background-color: '#000';
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这写起来得多麻烦啊,而且难以维护。

    好在我们有CSS变量,早在2017年,微软宣布Edge浏览器将支持CSS变量,现在几乎所有的浏览器都已经支持了这个功能。(IE:啊这?)

    css变量也是变量,就像js一样,先声明,再读取。

    body {
      --text-color: red;
    }
    .box {
      color: var(--text-color);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    已经出来很多年了,今天就不详细介绍了,有兴趣的推荐阅读 阮一峰老师的《CSS 变量教程》

    今天就用vue3项目来写一个基于css变量实现的主题切换demo。

    创建一个vue3项目:

    npm create vue@latest
    
    • 1

    创建一个theme.css文件。

    /**
      *默认主题
      */
    :root {
      --bg: #fff;
      --text-color: #000;
    }
    /**
      *添加属性,用来控制暗黑模式时的样式
      */
    html[data-theme="dark"] {
      --bg: #000;
      --text-color: #fff;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    或者像vue文档中一样使用class,如下所示:

    :root {
      --bg: #fff;
      --text-color: #000;
    }
    html.dark {
      --bg: #000;
      --text-color: #fff;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    但是如果某个页面内无意中野使用到同名dark这个class,可能会造成影响,我这里还是用属性。

    main.js中引入一下theme.css

    import './assets/theme.css'
    
    import { createApp } from 'vue'
    import App from './App.vue'
    
    createApp(App).mount('#app')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    App.vue style中调用一下变量,并动态改变data-theme的值:

    <template>
      <main>
        <p>主题切换demop>
        <button @click="change">切换button>
      main>
    template>
    <script>
      let theme = 'light'
      const change = () => {
        theme = theme === 'light' ? 'dark' : 'light'
        document.documentElement.setAttribute('data-theme', theme)
      }
    script>
    
    <style scoped>
    main {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      background-color: var(--bg);
      color: var(--text-color);
    }
    p {
      margin: 20px 0;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    看看效果:

    在这里插入图片描述

    功能基本上已经实现了,再来把这个切换操作封装成一个组件,并全局实时共享主题数据。

    创建一个useTheme.js,用来执行设置属性的操作:

    import { ref, watchEffect } from 'vue'
    
    // 默认用亮色
    const theme = ref('light')
    
    // 每次改变都设置一下
    watchEffect(() => {
      document.documentElement.setAttribute('data-theme', theme.value)
    })
    
    export default function useTheme() {
      return {
        theme
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    创建一个switch-theme.vue组件,仅仅用来改变theme的值:

    <template>
      <el-switch 
        v-model="theme"
        :active-action-icon="Moon"
        :inactive-action-icon="Sunny"
        active-color="#2f2f2f"
        active-value="dark"
        inactive-value="light"
        @change="changeDark"
      >el-switch>
    template>
    
    <script setup>
      import { Sunny, Moon } from '@element-plus/icons-vue'
      import useTheme from '../hooks/useTheme'
      const { theme } = useTheme()
      const changeDark = (data) => {
        theme.value === data
      }
    script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    改一下App.vue文件,引入并使用ThemeSwitch组件和useTheme Hook:

    <template>
      <main>
        <p>主题切换demop>
        <ThemeSwitch>ThemeSwitch>
        <p>当前主题:{{theme}}p>
      main>
    template>
    
    <script setup>
      import ThemeSwitch from './components/theme-switch.vue'
      import useTheme from './hooks/useTheme'
    
      const { theme } = useTheme()
    script>
    
    <style scoped>
    main {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100vh;
      background-color: var(--bg);
      color: var(--text-color);
    }
    p {
      margin: 20px 0;
    }
    style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    再看看效果:

    在这里插入图片描述

    现在由一个专门的组件用来控制切换主题,并且不同组件内也都能共享theme变量了。

    最后再优化一下,目前默认是亮色,切换到暗色以后再刷新页面,又会回到亮色,可以把theme变量存到localstorage

    修改一下useTheme.js:

    import { ref, watchEffect } from 'vue'
    
    // 从取缓存中取值,给个默认值。
    const theme = ref(localStorage.getItem('theme') || 'light')
    
    // 每次改变都设置一下属性,并存到缓存中。
    watchEffect(() => {
      document.documentElement.setAttribute('data-theme', theme.value)
      localStorage.setItem('theme', theme.value)
    })
    
    export default function useTheme() {
      return {
        theme
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    全部代码见Github

  • 相关阅读:
    如何使用注解管理Spring Bean的生命周期呢?
    Linux命令之head(13)
    Flink 系统性学习笔记系列
    深究数据库E-R模型设计
    vulfocus靶场之redis命令执行cve-2022-0543漏洞复现
    LeetCode每日一题(1024. Video Stitching)
    matlab中实现画函数图像添加坐标轴
    ​标签体系、用户画像、用户分群的区别?​
    Flutter 教程之 轮播图组件实现滚动视觉差(教程含源码)
    openmp 通用核心 学习 1
  • 原文地址:https://blog.csdn.net/xinTianou123/article/details/133380438