uniapp 小程序和nvue App适配深色/暗黑模式踩坑

4,501 阅读6分钟

兄弟篇:开发微信小程序 + nvue App踩坑篇

预览

小程序码

image.png

浅色

IMG_6752.JPEG

深色

IMG_6753.JPEG

介绍

深色模式(Dark Mode),微信从iOS客户端 7.0.12、Android 7.0.13、ios13开始正式支持 DarkMode,uniapp HBuilder X 3.6.9+ 也从开始,为开发者提供应用内的 DarkMode 适配能力。uniapp 深色模式适配

小程序端、uniapp App端适配深色模式,没有什么难度,通过开启深色模式配置+各页面通过css媒体查询适配深色主题,即可完成。但是对于nvue来说,不支持媒体查询,我们就要另辟蹊径了。下面我们来看看,如何适配。

配置

1. 小程序端开启支持深色模式开关

// manifest.json => mp-weixin
"mp-weixin": {
  "darkmode": true, // 开启支持深色模式
  "themeLocation": "theme.json", // 深色模式JSON文件,如果 theme.json 在根目录可省略
  ...
}

2. App端开启支持深色模式开关

// manifest.json => app-plus
"app-plus": {
  "darkmode": true, // 开启支持深色模式
  "themeLocation": "theme.json", // 深色模式JSON文件,如果 theme.json 在根目录可省略
  // ios底部安全区域颜色(⚠️注意:uniapp官方文档说要放在plus属性下,但其实放在app-plus下才会生效)
  "safearea": {
    "background": "#ffffff",
    "backgroundDark": "#1e1e1e", // HBuilderX 3.1.19+支持  
    "bottom": {
      "offset": "auto"
    }
  }
  ...
},
// manifest.json => plus
"plus": {
  "distribute": {
    "apple": {
      "UIUserInterfaceStyle": "Automatic", // 不推荐设置设置light或dark后,否则将无法跟随系统
      "defaultTheme": "auto" // HBuilderX 3.6.10及以上版本支持  
    },
    "google": {
      "defaultNightMode": "auto"
    }
  }
  ...
}

3. src 文件夹下新增 theme.json 文件

{
  // 浅色模式
  "light": {
    "navBgColor": "#fff", // 导航栏背景色
    "navTxtStyle": "black", // 导航栏文字颜色
    "bgColor": "#f5f6f7", // 页面背景色
    "bgTxtStyle": "light", // 下拉 loading 的样式,仅支持 dark/light
    "bgColorTop": "#eeeeee", // 顶部窗口的背景色(bounce回弹区域)
    "bgColorBottom": "#efefef", // 底部窗口的背景色(bounce回弹区域)
    "tabFontColor": "#666666", // tabbar 上的文字默认颜色
    "tabSelectedColor": "#FF9B35", // tabbar 上的文字选中颜色
    "tabBgColor": "#ffffff", // tabbar 背景色
    "tabBorderStyle": "white", // tabbar 上边框的颜色,可选值 black/white,也支持其他颜色值
    "iconPath1": "/static/xxx.png", // tabbar第二个图标默认路径
    "selectedIconPath1": "/static/xxx.png", // tabbar第二个图标选中路径
    "iconPath2": "/static/xxx.png",
    "selectedIconPath2": "/static/xxx.png"
  },
  // 深色模式
  "dark": {
    "navBgColor": "#1d1d1d", // 导航栏背景色
    "navTxtStyle": "white", // 导航栏文字颜色
    "bgColor": "#1e1e1e", // 页面背景色
    "bgTxtStyle": "dark", // 下拉 loading 的样式,仅支持 dark/light
    "bgColorTop": "#1e1e1e", // 顶部窗口的背景色(bounce回弹区域)
    "bgColorBottom": "#1e1e1e", // 底部窗口的背景色(bounce回弹区域)
    "tabFontColor": "#bbb", // tabbar 上的文字默认颜色
    "tabSelectedColor": "#FF9B35", // tabbar 上的文字选中颜色
    "tabBgColor": "#212121", // tabbar 背景色
    "tabBorderStyle": "black", // tabbar 上边框的颜色,可选值 black/white,也支持其他颜色值
    "iconPath1": "/static/xxx.png", // tabbar第一个图标默认路径
    "selectedIconPath1": "/static/xxx.png", // tabbar第一个图标选中路径
    "iconPath2": "/static/xxx.png", // tabbar第二个图标默认路径
    "selectedIconPath2": "/static/xxx.png" // tabbar第二个图标选中路径
   }
}

4. 修改 pages.json 文件

// 取theme.json配置的相关属性,以@开头
{
  ...
  // 全局,需要才配置
  "globalStyle": {
    // 导航栏字体颜色
    "navigationBarTextStyle": "@navTxtStyle",
    // 导航栏背景色
    "navigationBarBackgroundColor": "@navBgColor",
    // 导航栏背景底色
    "backgroundColor": "@bgColor"
  },
  // 原生tabbar配置,与theme.json对应即可
  "tabBar": {
    "color": "@tabFontColor",
    "selectedColor": "@tabSelectedColor",
    "backgroundColor": "@tabBgColor",
    "borderStyle": "@tabBorderStyle",
    "list": [
      {
        "selectedIconPath": "@selectedIconPath1",
        "iconPath": "@iconPath1",
        "pagePath": "pages/xxx",
        "text": "首页"
      },
      {
        "selectedIconPath": "@selectedIconPath2",
        "iconPath": "@iconPath2",
        "pagePath": "pages/xxx",
        "text": "我的"
      }
    ]
  }
  ...
}

5. App.vue

<script>
export default {
  onLaunch (options) {
    /** @⚠️:App端需要调用setUIStyle,否则无法使用深色模式,官方回复下版修复此问题 **/
    // #ifdef APP-PLUS
    plus.nativeUI.setUIStyle('auto')
    // #endif
  }
}
</script>
<style lang='scss'>
</style>

6. 自定义导航栏适配深色模式

如果自定义导航栏,时间、电池等文字信息,无法自动变化,我们需要根据当前主题,动态切换。 在 main.js 中,封装一个修改导航栏标题、电量等信息的方法 uni.setNavStyle,我们可以直接绑定在uni上。

uni.setNavStyle = () => {
  const theme = uni.getSystemInfoSync().theme
  // App端
  // #ifdef APP-PLUS
  plus.navigator.setStatusBarStyle(theme === 'dark' ? 'light' : 'dark') // 只支持dark和light
  // #endif

  // 小程序端
  // #ifdef MP-WEIXIN
  uni.setNavigationBarColor({
    frontColor: theme === 'dark' ? '#fff' : '#000',
    backgroundColor: theme === 'dark' ? '#000' : '#fff'
  })
  // #endif
}

然后在我们有自定义导航栏的vue或nvue文件中,在 onLoad 执行一次 uni.setNavStyle(),然后调用 uni.onThemeChange ,即每次主题变化时,执行一次 uni.setNavStyle()

<script>
  import { mapState } from 'vuex'
  export default {
    onLoad () {
      uni.setNavStyle()
      // 用户切换主题,动态修改状态栏颜色
      uni.onThemeChange(() => {
        uni.setNavStyle()
      })
    }
  }
</script>

7. 页面中通过css媒体查询适配深色模式

在 css 中,小程序端及App端(非nvue)支持通过媒体查询 prefers-color-scheme 适配不同主题,与 Web 中适配方式一致,例如:

/* 浅色模式的样式 start */
.box {
  background: #fff;
}
.text {
  color: #000;
}
/* 浅色模式的样式 end */

/* 深色模式的样式 start */
@media (prefers-color-scheme: dark) {
  .box {
    background: #000;
  }
  .text {
    color: #fff;
  }
}
/* 深色模式的样式 end */

到这里,小程序或者App(非nvue)端的深色模式就适配完成了,需要⚠️注意的是,小程序中可以边修改边在开发者工具看到深色模式的效果,但是App端(非nvue)需要【云打包】后在手机或者模拟器上才能看到效果。

但是,对于 nvue 的App,打包后也是无法看到效果的,因为nvue是不支持媒体查询的。(详见nvue中css样式注意事项),所以我们需要另辟蹊径。

首先想到的是,给 nvuetemplate 中最外层元素根据当前主题动态添加 class="isDark",但是nvue 一开始是不支持 后代选择器 等css选择器的,这点就很糟心了。

还好,在HBuilderX 3.1.0+ 开始支持新的样式编译模式 "nvueStyleCompiler": "uni-app" (详见# nvue 样式编译模式介绍),该模式可以在 weex 原有样式基础上支持组合选择器(相邻兄弟选择器、普通兄弟选择器、子选择器、后代选择器),这就很舒服了。

所以,刚刚的动态 class 方案,就可以实现了。

8. nvue适配深色模式——设置新的编译模式

manifest.json 中,新增 nvueCompiler 属性

...
"app-plus": {
  ...
  //使nvue支持相邻兄弟选择器、普通兄弟选择器、子选择器、后代选择器
  "nvueCompiler": "uni-app",
  "nvueStyleCompiler": "uni-app"
  ...
}
...

9. nvue适配深色模式——状态机 store 声明 isDark

首先,我们在store中声明一个布尔值变量 isDark,默认值是根据当前主题色判断是否是深色模式

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    isDark: uni.getSystemInfoSync().theme === 'dark'
  }
})
export default store

10. nvue适配深色模式——App.vue文件中,监听主题变化,动态修改 store.state.isDark

<script>
// 引入store
import store from './store/index'
export default {
  onThemeChange ({theme}) {
    // 用户切换主题时,将新的主题存在store里,这也是实现nvue深色模式的关键
    store.state.isDark = theme === 'dark'
  }
}
</script>

11、nvue适配深色模式——动态class

在文件中,引入 store.state.isDark 或者使用辅助函数 mapState 引入

<template>
  <view class="wrap" :class="{ isDark }">
    <text class="text">文字</text>
  </view>
</template>

<script>
  import { mapState } from 'vuex'
  export default {
    computed: {
      ...mapState(['isDark'])
    }
  }
</script>

<style lang='scss' scoped>
  /* 浅色模式的样式 start */
  .wrap {
    background: #fff;
    .text {
      color: #000;
    }
  }
  /* 浅色模式的样式 end */

  /* 深色模式的样式 start */
  .isDark {
    &.wrap {
     background: #000;
     .text {
       color: #fff;
     }
    }
  }
  /* 深色模式的样式 end */
</style>

到这里,小程序 + nvue开发的App就适配了深色模式。