uniapp主题切换功能,适配H5、小程序

实现方法

方法 性能消耗 维护成本 适用场景
内联样式 较高 小程序
CSS变量+属性选择器 H5
混合方案 中等 跨平台项目

优势特点

  1. 性能优化:
    1. H5端使用CSS原生变量切换
    2. 小程序端使用高效样式字符串生成
    3. 切换动画流畅
  2. 维护性提升
    1. 主题配置集中管理
    2. 新增主题只需要拓展vars对象

使用pinia管理主题

第一步、定义主题和CSS变量

javascript 复制代码
state: () => {
  return {
    mode: uni.getStorageSync('theme') || 'light',
    vars: {
      light: { '--primary': '#007AFF', '--bg': '#FFFFFF' },
      dark: { '--primary': '#0BB640', '--bg': '#1A1A1A' },
    },
  }
},

第二步、实现主题切换方法

javascript 复制代码
  actions: {
    // 统一切换入口
    toggle(themeMode) {
      // 判断是否传递主题,如果没有则在dark和light之间切换
      if (themeMode) {
        this.mode = themeMode
      } else {
        this.mode = this.mode === 'light' ? 'dark' : 'light'
      }
      // 本地存储
      uni.setStorageSync('theme', this.mode)
    },
  }

第三步、实现原生组件适配

javascript 复制代码
  actions: {
    // 原生组件适配
    updateNative() {
      // 判断终端类型
      const isMP = process.env.UNI_PLATFORM?.startsWith('mp-')
      const vars = this.vars[this.mode]

      if (isMP) {
        // 设置顶部导航栏样式
        uni.setNavigationBarColor({
          backgroundColor: vars['--bg'],
          frontColor: this.mode === 'dark' ? '#ffffff' : '#000000',
        })
        // code......
      }
    },
  }

第四步、H5根属性更新

javascript 复制代码
actions: {
  // H5根属性更新
  updateRootAttribute() {
    const isH5 = process.env.UNI_PLATFORM === 'h5'
    if (isH5) {
      // 更新HTML中的data-theme属性标识
      document.documentElement.setAttribute('data-theme', this.mode)
    }
  },
},

完整代码

javascript 复制代码
import { defineStore } from 'pinia'

export const useThemeStore = defineStore('theme', {
  state: () => {
    return {
      mode: uni.getStorageSync('theme') || 'light',
      vars: {
        light: { '--primary': '#007AFF', '--bg': '#FFFFFF' },
        dark: { '--primary': '#0BB640', '--bg': '#1A1A1A' },
      },
    }
  },
  actions: {
    // 统一切换入口
    toggle(themeMode) {
      if (themeMode) {
        this.mode = themeMode
      } else {
        this.mode = this.mode === 'light' ? 'dark' : 'light'
      }
      uni.setStorageSync('theme', this.mode)

      // 多端样式更新
      this.updateNative()
      // #ifdef H5
      this.updateRootAttribute()
      // #endif
    },

    // 原生组件适配
    updateNative() {
      const isMP = process.env.UNI_PLATFORM?.startsWith('mp-')
      const vars = this.vars[this.mode]

      if (isMP) {
        uni.setNavigationBarColor({
          backgroundColor: vars['--bg'],
          frontColor: this.mode === 'dark' ? '#ffffff' : '#000000',
        })
      }
    },

    // H5根属性更新
    updateRootAttribute() {
      const isH5 = process.env.UNI_PLATFORM === 'h5'
      if (isH5) {
        document.documentElement.setAttribute('data-theme', this.mode)
      }
    },
  },
})

定义CSS变量

定义CSS变量,H5中使用

plain 复制代码
/* 多端兼容方案 */
:root {
  // 默认主题(编译时注入小程序)
  --primary: #007aff;
  --bg: #ffffff;
}

// H5动态主题(运行时切换)
@media all {
  [data-theme='light'] {
    --primary: #007aff;
    --bg: #ffffff;
  }
  [data-theme='dark'] {
    --primary: #0bb640;
    --bg: #1a1a1a;
  }
}

在App.vue中导入使用

vue 复制代码
<style lang="scss">
@import '@/styles/index.scss';
</style>

创建一个Hook

通过Hook来管理主题的切换、样式格式化等,避免重复导入Store

javascript 复制代码
import { computed } from 'vue'
import { useThemeStore } from '@/stores/theme'

export const useTheme = () => {
  const themeStore = useThemeStore()

  // 响应式主题变量
  const themeVars = computed(() => {
    const result = {
      // 小程序端需要转换的样式
      mpStyle: null,
      // H5数据属性
      dataTheme: themeStore.mode,
    }
    if (isMP.value) {
      result.mpStyle = Object.entries(themeStore.vars[themeStore.mode])
        .map(([k, v]) => `${k}:${v}`)
        .join(';')
    }
    return result
  })
  const isMP = computed(() => process.env.UNI_PLATFORM?.startsWith('mp-'))

  return {
    isMP,
    toggle: themeStore.toggle, // 切换方法
    currentMode: computed(() => themeStore.mode), // 当前模式
    themeVars, // 样式绑定对象
  }
}

在组件中使用

最主要的代码是::style="{ ...themeVars.mpStyle }",这样就可以实现在小程序主题切换时变量自动更新

vue 复制代码
<template>
  <view class="container" :style="{ ...themeVars.mpStyle }">
    <view class="w-150px box">
      <text>主题切换测试</text>
      <text class="iconfont icon-sousuo"></text>
      <button type="primary" @tap="() => toggle()">切换主题</button>
    </view>
  </view>
</template>
<script setup>
import { useTheme } from '@/Hooks/useTheme'

const { themeVars, toggle } = useTheme()
</script>
<style lang="scss" scoped>
.container {
  .box {
    background-color: var(--bg);
    font-size: 18px;
    font-weight: 500;
    line-height: 32px;
    color: var(--primary);
  }
}
</style>

主题初始化

在App.vue文件中添加如下代码

vue 复制代码
onLaunch(() => {
  // 主题初始化
  const savedTheme = uni.getStorageSync('theme')
  if (savedTheme) {
    themeStore.toggle(savedTheme)
  }
})

优化拓展

添加transition

在css或scss文件中添加如下代码,使主题切换时更加流畅的过渡,避免生硬切换

plain 复制代码
* {
  transition: background-color 0.3s, background 0.3s, color 0.3s;
}

监听系统主题变化

在App.vue文件中使用uni.onThemeChange监听系统主题变化,并同步小程序/H5主题变化

vue 复制代码
onLaunch(() => {
  // 监听系统主题变化
  uni.onThemeChange(({ theme }) => {
    const systemTheme = theme === 'dark' ? 'dark' : 'light'
    themeStore.toggle(systemTheme)
  })
})

新增主题?

如果想要新增主题,只需要在stores/theme.jsstyle/index.scss文件中添加对应主题的CSS变量,theme.js中定义小程序的主题,index.scss定义H5的主题,如:

vue 复制代码
state: () => {
  return {
    vars: {
      light: { '--primary': '#007AFF', '--bg': '#FFFFFF' },
      dark: { '--primary': '#0BB640', '--bg': '#1A1A1A' },
      red: {
        // ......新变量
      }
    },
  }
},
css 复制代码
// H5动态主题(运行时切换)
@media all {
  [data-theme='light'] {
    --primary: #007aff;
    --bg: #ffffff;
  }
  [data-theme='dark'] {
    --primary: #0bb640;
    --bg: #1a1a1a;
  }
  [data-theme='red'] {
    /* ......新变量 */
  }
}

注意事项

  1. :root选择器:用于匹配文档的根元素(在 HTML 文档中即 标签)。它是定义全局 CSS 变量的最佳位置,尤其在主题切换场景中发挥关键作用。
  2. @media all媒体查询:所有设备上都生效
  3. 请不要将index.scss中的代码放到uni.scss中,这样可能导致切换主题时不生效
相关推荐
暮雨哀尘5 分钟前
微信小程序开发:微信小程序组件应用研究
算法·微信·微信小程序·小程序·notepad++·微信公众平台·组件
西陵10 分钟前
一文带你吃透前端网站嵌入设计
前端·javascript·架构
给钱,谢谢!12 分钟前
记录vite引入sass预编译报错error during build: [vite:css] [sass] Undefined variable.问题
前端·css·sass·vite
拉不动的猪30 分钟前
react常规面试题
前端·javascript·面试
NaZiMeKiY31 分钟前
HTML5前端第六章节
前端·html·html5·1024程序员节
李小白6640 分钟前
Vue背景介绍+声明式渲染+数据响应式
前端·javascript·vue.js
*TQK*40 分钟前
CSS学习笔记5——渐变属性+盒子模型阶段案例
css·笔记·学习
烛阴1 小时前
JavaScript yield与异步编程
前端·javascript
知识分享小能手1 小时前
CSS3学习教程,从入门到精通,CSS3 媒体查询实现响应式布局语法指南(21)
前端·css·学习·css3·html5·媒体
狂团商城小师妹1 小时前
经销商订货管理系统小程序PHP+uniapp
微信·微信小程序·小程序·uni-app·php·微信公众平台