夜雨聆风学习资料网

ARTICLE · 1144094

uni-app 暗黑模式与主题切换:CSS 变量三档方案

uni-app 暗黑模式与主题切换:CSS 变量三档方案
用户对暗黑模式的期待早就从「加分项」变成「标配」了,尤其是夜间使用的小程序。这篇记录我把三档主题(跟随系统、浅色、深色)落到 uni-app 三端的完整过程:CSS 变量打底、各端的跟随机制差异、手动切换的持久化,以及第三方组件和图片的适配。

一、先把三档的交互定下来

主题切换不是「开/关」两档,成熟的产品都是三档:跟随系统、浅色、深色。默认跟随系统,用户手动选了就用手动的。这个决策直接影响实现方式:要能同时拿到「系统当前是什么」和「用户选了什么」,再合成最终值。

用户选择存 Pinia 加持久化(前面 Pinia 持久化那篇的方案直接复用)

二、CSS 变量打底:颜色只定义两遍

整套方案的地基是把颜色收敛成语义化 CSS 变量,页面里绝不写死色值:

/* uni.scss 或 App.vue 全局样式 */page {  --bg-page: #f5f6f7;  --bg-card: #ffffff;  --text-primary: #333333;  --text-secondary: #666666;  --brand: #09BB07;}/* 深色档:根节点换变量值,页面代码零改动 */.page-dark {  --bg-page: #111111;  --bg-card: #1e1e1e;  --text-primary: #e8e8e8;  --text-secondary: #999999;  --brand: #07c160;}.card {  background: var(--bg-card);  color: var(--text-primary);}

深色模式不是简单反色,变量值要重新调:深色底上的「次要文字」不能直接用深色底上的主色反推,对比度要按 WCAG 标准过一遍,纯黑背景换带一点灰的 #111,视觉舒适度差很多。

三、切换机制:三端各有各的监听

// utils/theme.js —— 合成最终主题const themeStore = useThemeStore()   // 持久化的用户选择function resolveTheme() {  if (themeStore.choice !== 'auto') return themeStore.choice  // auto 档:读系统状态  const sys = uni.getAppBaseInfo().theme || 'light'  return sys}// 应用到页面根节点:H5 绑在根元素 class,小程序换 page-metafunction applyTheme(theme) {  themeStore.current = theme}
<!-- 页面里用 page-meta 改页面根节点背景,三端通用 --><page-meta :page-style="`background-color: var(--bg-page)`" />

系统状态变化的监听,小程序和 App 用 uni.onThemeChange,H5 用 matchMedia('(prefers-color-scheme: dark)') 加 change 监听。三端监听方式不同,但回调里做的事一样:用户在 auto 档时重算主题并更新变量。

小程序要支持 onThemeChange 和 darkmode,得在 app.json(uni-app 项目里是 manifest.json 的 mp-weixin 节点)声明 "darkmode": true,没声明时这个监听不会触发,我一开始就栽在这里,代码没问题就是不回调。

四、别漏掉的角落:图片和第三方组件

•图片:写死白底的 PNG 在深色下像补丁。图标用 SVG 或字体图标跟 CSS 变量换色;必要的小插画准备两套,按主题切换 src

•第三方组件库:选型时就要确认暗黑支持程度,后面组件库选型那篇会专门对比。不支持的就给组件套一层变量覆盖

•导航栏和 tabbar:原生导航栏的颜色不走 CSS 变量,主题切换时要同步调 uni.setNavigationBarColor和 uni.setTabBarStyle

· · ·

这篇的方案可以概括成三层:CSS 变量把颜色收敛成语义 token,深浅两套值只定义两遍;用户选择持久化到 Pinia store,auto 档按端监听系统变化重算;最后把图片、三方组件、原生导航栏这些「变量管不到的角落」逐个适配掉。三端行为对齐之后,暗黑模式就从「敢不敢上」变成了「按清单扫一遍」的事。

觉得有用的话,点赞、分享、推荐三连支持一下。

相关学习资料