uniapp 微信小程序暗色模式

244 阅读3分钟

最近给我们的小程序加了一个暗色模式切换

image.png

image.png

整体思路:两套颜色 + 两种触发方式

核心原理:用 CSS 变量(--theme-bg-primary 这种)来定义所有颜色,亮色和暗色分别给这些变量赋不同的值,组件本身只用变量,不写死颜色


两种触发方式

方式一:跟随系统(自动)

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) { /* 切换变量值 */ }
}

这个按照手机系统来

方式二:用户手动切换

// store/modules/theme.js
document.documentElement.setAttribute('data-theme', 'dark')

在 <html> 根节点上加 data-theme="dark" 属性,CSS 里 [data-theme='dark'] 选择器就生效了,优先级比媒体查询高,所以能覆盖系统设置。

储存到 storage 里面


文件分工

文件干啥的
styles/theme.scss定义所有 CSS 变量,亮色/暗色两套值
styles/dark-override.scss把具体 class 名和变量绑定(大量 !important)
store/modules/theme.jsJS 层管理状态,读/写本地存储,监听系统主题变化
App.vue启动时初始化主题,监听系统 onThemeChange 事件

代码

app.vue
<!-- 全局主题 CSS 自定义属性(非 scoped) -->
<style lang="scss">
@import './styles/theme.scss';
</style>

<!-- 全局暗色模式覆盖(非 scoped):覆盖手动切换 [data-theme='dark'] -->
<style lang="scss">
@import './styles/dark-override.scss';
</style>
//theme.scss
/* 亮色(默认) */
:root, [data-theme='light'] {
  --theme-bg-primary: #ffffff;
  --theme-bg-card: #ffffff;
  --theme-text-primary: #101114;
  --theme-input-bg: #ffffff;
}

/* 暗色 — 跟随系统 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --theme-bg-primary: #15171f;
    --theme-bg-card: #1c1e28;
    --theme-text-primary: #f0f0f0;
    --theme-input-bg: #222222;
  }
}

/* 暗色 — 手动切换(优先级更高) */
[data-theme='dark'] {
  --theme-bg-primary: #15171f;
  --theme-bg-card: #1c1e28;
  --theme-text-primary: #f0f0f0;
}

// dark-override.scss

@mixin dark-overrides {
  .page-wrap, .publish-page { background: var(--theme-bg-primary) !important; }
  .info-card, .menu-card     { background: var(--theme-bg-card) !important; }
  .card-title, .user-name    { color: var(--theme-text-primary) !important; }
  .title-input               { background: var(--theme-input-bg) !important; }
}

/* 系统自动 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) { @include dark-overrides; }
}

/* 手动切换 */
[data-theme='dark'] { @include dark-overrides; }

// 切换主题
toggleThemeMode({ commit, state }) {
  const newMode = state.themeMode === 'light' ? 'dark' : 'light'
  commit('SET_THEME_MODE', newMode)
  commit('SET_THEME_OVERRIDE', true)

  uni.setStorageSync('themeMode', newMode)      // 持久化
  uni.setStorageSync('themeOverride', 'true')

  this.dispatch('theme/applyThemeMode')
},

// 应用到 DOM
applyThemeMode({ state }) {
  if (typeof document !== 'undefined') {
    if (state.themeOverride) {
      // 手动模式:加 data-theme 属性,覆盖媒体查询
      document.documentElement.setAttribute('data-theme', state.themeMode)
    } else {
      // 跟随系统:移除属性,让 @media 自动响应
      document.documentElement.removeAttribute('data-theme')
    }
  }
},

接下来是比较重点的,上面那些是网页常做的,而小程序要考虑导航栏这些东西的颜色

//app.vue
  onShow() {
    // 每次页面显示时恢复导航栏颜色(纯跟随系统时 @navBgColor 自动生效)
    try {
      const store = this.$store
      const themeMode = store.getters['theme/themeMode']
      const themeOverride = store.getters['theme/themeOverride']
      if (themeOverride && (themeMode === 'dark' || themeMode === 'light')) {
        uni.setNavigationBarColor({
          frontColor: themeMode === 'dark' ? '#ffffff' : '#000000',
          backgroundColor: themeMode === 'dark' ? '#1a1a1a' : '#ffffff',
        })
      }
    } catch (e) {
      // 低版本可能不支持
    }
  },
  onHide() {
    // App hidden
  },
  // 监听系统主题切换(微信小程序 2.11.0+ 支持)
  onThemeChange(res) {
    this.handleSystemThemeChange(res.theme)
  },

手动切换的时候要使用setNavigationBarColor强制修改导航栏、tabBar、页面背景的颜色

{
  "pages": [
      {
      "root": "modules/pages/DiscoverSearch",
      "name": "search",
      "pages": [
        {
          "path": "DiscoverSearch",
          "style": {
            "navigationBarTitleText": "搜索",
            "navigationBarBackgroundColor": "@navBgColor",
            "navigationStyle": "default"
          }
        }
      ]
    },
  ],
  "globalStyle": {
    "navigationBarTextStyle": "@navTxtStyle",
    "navigationBarTitleText": "gallery",
    "navigationBarBackgroundColor": "@navBgColor",
    "backgroundColor": "@bgColor",
    "backgroundTextStyle": "@bgTxtStyle",
    "navigationStyle": "custom",
    "app-plus": {
      "softinputmode": "adjustPan"
    },
  }
}

这里要用@navBgColor这样的用 @变量名 语法引用 theme.json 里的 key


这套方案是 CSS 变量 + 媒体查询 + 属性选择器 的组合