在 Vue 项目中,如何实现主题切换功能,支持深色模式或自定义配色方案?

各位靓仔靓女,早上好!今天咱们来聊聊 Vue 项目里如何玩转主题切换,让你的项目瞬间高大上起来!咱们的目标是,不仅要支持深色模式,还要能让用户自定义配色,玩出自己的风格。

开场白:主题切换,不仅仅是变个颜色

咱们先别着急上代码,先想想,主题切换到底是个啥?它不仅仅是把背景颜色从白色变成黑色那么简单。一个好的主题切换方案,应该能做到:

  • 全局生效: 一旦切换,整个项目都跟着变。
  • 可维护性: 修改主题配置要方便,别改一个颜色牵一发动全身。
  • 用户体验: 切换要平滑,别让用户觉得“Duang”的一下闪瞎眼。
  • 可扩展性: 以后想增加更多主题,要容易扩展。

OK,有了这些目标,咱们就可以开始撸代码了!

第一步:搭建基础框架

首先,咱们建一个简单的 Vue 项目。如果已经有了,那就跳过这一步。

vue create theme-switch-demo

一路回车,选择你喜欢的配置。

建好项目后,咱们先来搭个简单的页面,方便测试主题切换效果。修改 src/App.vue

<template>
  <div id="app" :class="theme">
    <h1>主题切换演示</h1>
    <p>这是一段普通的文本。</p>
    <button @click="toggleTheme">切换主题</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      theme: 'light' // 默认主题
    };
  },
  methods: {
    toggleTheme() {
      this.theme = this.theme === 'light' ? 'dark' : 'light';
    }
  }
};
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.light {
  background-color: #fff;
  color: #2c3e50;
}

.dark {
  background-color: #222;
  color: #eee;
}
</style>

这段代码很简单:

  • theme 数据属性控制当前主题。
  • toggleTheme 方法切换主题。
  • #app 元素绑定了 theme 类名,根据 theme 的值应用不同的 CSS 样式。

现在运行项目,点击按钮,就可以在亮色和暗色主题之间切换了。

第二步:使用 CSS Variables (Custom Properties)

上面的代码虽然能实现主题切换,但是 CSS 样式是写死的,不够灵活。咱们来用 CSS Variables 改造一下。

修改 src/App.vue<style> 部分:

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: var(--text-color); /* 使用 CSS Variable */
  margin-top: 60px;
  background-color: var(--background-color); /* 使用 CSS Variable */
}

/* 定义 light 主题的 CSS Variables */
.light {
  --background-color: #fff;
  --text-color: #2c3e50;
}

/* 定义 dark 主题的 CSS Variables */
.dark {
  --background-color: #222;
  --text-color: #eee;
}
</style>

这次,咱们把颜色值换成了 CSS Variables (--background-color--text-color)。lightdark 类名里定义了这些变量的值。

这样做的好处是:

  • 更容易修改: 修改主题颜色只需要修改 CSS Variables 的值。
  • 更灵活: 可以在任何地方使用这些变量,实现更精细的样式控制。

第三步:更优雅的主题管理

虽然现在能切换主题了,但是主题配置都写在 App.vue 里,不太优雅。咱们来把它抽离出来,放到一个单独的文件里。

新建一个文件 src/themes.js

const themes = {
  light: {
    '--background-color': '#fff',
    '--text-color': '#2c3e50',
    '--button-background-color': '#4CAF50',
    '--button-text-color': '#fff'
  },
  dark: {
    '--background-color': '#222',
    '--text-color': '#eee',
    '--button-background-color': '#333',
    '--button-text-color': '#ddd'
  }
};

export default themes;

这个文件定义了一个 themes 对象,包含了 lightdark 两个主题的配置。每个主题都是一个对象,包含了 CSS Variables 和它们的值。

修改 src/App.vue

<template>
  <div id="app" :style="themeStyles">
    <h1>主题切换演示</h1>
    <p>这是一段普通的文本。</p>
    <button @click="toggleTheme" :style="{ backgroundColor: themeStyles['--button-background-color'], color: themeStyles['--button-text-color'] }">切换主题</button>
  </div>
</template>

<script>
import themes from './themes';

export default {
  name: 'App',
  data() {
    return {
      currentTheme: 'light'
    };
  },
  computed: {
    themeStyles() {
      return themes[this.currentTheme];
    }
  },
  methods: {
    toggleTheme() {
      this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
    }
  }
};
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: var(--text-color);
  margin-top: 60px;
  background-color: var(--background-color);
}

button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}
</style>

这次,咱们做了以下修改:

  • 引入了 themes 对象。
  • 使用 currentTheme 数据属性来记录当前主题。
  • 使用 computed 属性 themeStyles 来获取当前主题的 CSS Variables。
  • 通过 :style 指令将 themeStyles 应用到 #app 元素上。
  • 通过 :style 指令将 themeStyles 应用到 button 元素上, 设置背景颜色和文字颜色.
  • 去掉了原本的.light和.dark class.

这样做的好处是:

  • 主题配置更集中: 所有的主题配置都在 themes.js 文件里,方便管理。
  • 代码更简洁: App.vue 的代码更干净,只负责切换主题和应用样式。
  • 更易扩展: 以后想增加更多主题,只需要在 themes.js 文件里添加新的配置。

第四步:持久化主题

现在,每次刷新页面,主题都会恢复到默认值。咱们来把主题保存到 localStorage 里,让用户下次打开页面时,还是上次选择的主题。

修改 src/App.vue

<template>
  <div id="app" :style="themeStyles">
    <h1>主题切换演示</h1>
    <p>这是一段普通的文本。</p>
    <button @click="toggleTheme" :style="{ backgroundColor: themeStyles['--button-background-color'], color: themeStyles['--button-text-color'] }">切换主题</button>
  </div>
</template>

<script>
import themes from './themes';

export default {
  name: 'App',
  data() {
    return {
      currentTheme: localStorage.getItem('theme') || 'light' // 从 localStorage 中读取主题
    };
  },
  computed: {
    themeStyles() {
      return themes[this.currentTheme];
    }
  },
  watch: {
    currentTheme(newTheme) {
      localStorage.setItem('theme', newTheme); // 保存主题到 localStorage
    }
  },
  methods: {
    toggleTheme() {
      this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
    }
  }
};
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: var(--text-color);
  margin-top: 60px;
  background-color: var(--background-color);
}

button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}
</style>

这次,咱们做了以下修改:

  • data 属性中,从 localStorage 中读取主题,如果没有,则使用默认主题 light
  • 使用 watch 监听 currentTheme 的变化,每次变化都把新的主题保存到 localStorage 中。

现在,刷新页面,主题也不会丢失了。

第五步:自定义配色方案

前面的代码只能切换预定义的主题,咱们来增加一个功能,让用户可以自定义配色方案。

首先,在页面上增加一些颜色选择器:

修改 src/App.vue<template> 部分:

<template>
  <div id="app" :style="themeStyles">
    <h1>主题切换演示</h1>
    <p>这是一段普通的文本。</p>
    <button @click="toggleTheme" :style="{ backgroundColor: themeStyles['--button-background-color'], color: themeStyles['--button-text-color'] }">切换主题</button>

    <h2>自定义配色</h2>
    <label>
      背景颜色:
      <input type="color" v-model="customTheme['--background-color']">
    </label>
    <label>
      文字颜色:
      <input type="color" v-model="customTheme['--text-color']">
    </label>
    <label>
      按钮背景颜色:
      <input type="color" v-model="customTheme['--button-background-color']">
    </label>
    <label>
      按钮文字颜色:
      <input type="color" v-model="customTheme['--button-text-color']">
    </label>
    <button @click="applyCustomTheme">应用自定义主题</button>
  </div>
</template>

这段代码增加了一些 input[type="color"] 元素,用户可以选择自己喜欢的颜色。

然后,修改 src/App.vue<script> 部分:

<script>
import themes from './themes';

export default {
  name: 'App',
  data() {
    return {
      currentTheme: localStorage.getItem('theme') || 'light',
      customTheme: { // 自定义主题
        '--background-color': '#fff',
        '--text-color': '#2c3e50',
        '--button-background-color': '#4CAF50',
        '--button-text-color': '#fff'
      },
      isCustomThemeApplied: false // 标记是否应用了自定义主题
    };
  },
  computed: {
    themeStyles() {
      return this.isCustomThemeApplied ? this.customTheme : themes[this.currentTheme];
    }
  },
  watch: {
    currentTheme(newTheme) {
      localStorage.setItem('theme', newTheme);
    }
  },
  methods: {
    toggleTheme() {
      this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
      this.isCustomThemeApplied = false; // 切换主题时,取消自定义主题
    },
    applyCustomTheme() {
      this.isCustomThemeApplied = true;
      localStorage.setItem('theme', 'custom'); // 保存自定义主题标记
      // 将自定义主题保存到 localStorage
      localStorage.setItem('customTheme', JSON.stringify(this.customTheme));
    },
  created() {
      // 在组件创建时从 localStorage 中加载自定义主题
      const storedCustomTheme = localStorage.getItem('customTheme');
      if (storedCustomTheme) {
        this.customTheme = JSON.parse(storedCustomTheme);
      }
      if (localStorage.getItem('theme') === 'custom') {
        this.isCustomThemeApplied = true;
        this.currentTheme = 'custom'; // 保持currentTheme同步
      }
    }
  }
};
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: var(--text-color);
  margin-top: 60px;
  background-color: var(--background-color);
}

button {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}
</style>

这次,咱们做了以下修改:

  • 增加了一个 customTheme 数据属性,用于存储自定义配色方案。
  • 增加了一个 isCustomThemeApplied 数据属性,用于标记是否应用了自定义主题。
  • 修改了 themeStyles 计算属性,如果 isCustomThemeAppliedtrue,则使用 customTheme,否则使用预定义的主题。
  • 增加了一个 applyCustomTheme 方法,用于应用自定义主题,并保存到 localStorage。
  • 在 created 生命周期中, 加载 localStorage 中的 customTheme.

现在,用户可以选择自己喜欢的颜色,然后点击“应用自定义主题”按钮,就可以看到效果了。

第六步:更进一步的思考

上面的代码已经能实现基本的主题切换和自定义配色方案了。但是,还可以更进一步的思考:

  • 主题变量的管理: 如果项目很大,主题变量很多,可以考虑使用一个专门的工具来管理主题变量,比如 Style Dictionary。
  • CSS Modules: 如果使用了 CSS Modules,可以把主题变量定义在一个单独的 CSS Module 文件里,然后在其他模块里引入。
  • Vuex: 如果项目使用了 Vuex,可以把主题状态放到 Vuex 里,方便在不同的组件之间共享。
  • Typescript: 如果项目使用了 Typescript,可以定义主题变量的类型,避免拼写错误。

总结:主题切换,任你玩转!

今天咱们一起学习了 Vue 项目中主题切换的实现方法,从最简单的亮色和暗色主题切换,到自定义配色方案,一步一步地让你的项目变得更加个性化。

记住,主题切换不仅仅是变个颜色,更重要的是要考虑到全局生效、可维护性、用户体验和可扩展性。

希望今天的分享对你有所帮助。下次有机会,咱们再聊聊其他的 Vue 技巧!

一些小技巧

技巧 说明
localStorage 用于持久化存储主题,避免刷新页面后丢失。
CSS Variables 用于定义主题颜色,方便修改和扩展。
computed 用于根据当前主题动态计算样式。
watch 用于监听主题的变化,并保存到 localStorage 中。
:style 用于动态绑定样式,将主题变量应用到元素上。
JSON.stringify和JSON.parse 用于将对象和字符串之间进行转换,方便存储到localStorage.

常见问题

  • Q: 如何让主题切换更平滑?

    • A: 可以使用 CSS transition 属性,为主题切换添加过渡效果。
  • Q: 如何支持更多的预定义主题?

    • A: 在 themes.js 文件里添加新的主题配置。
  • Q: 如何让用户可以保存多个自定义配色方案?

    • A: 可以使用一个数组来存储多个自定义配色方案,并提供一个界面让用户选择和管理这些方案。

最后的温馨提示

写代码就像谈恋爱,要用心,要投入,要不断学习和改进。希望你也能在编程的道路上找到属于自己的快乐!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注