你有没有留意过这样的应用:晚上打开变成深色背景,白天又变回浅色;或者电商大促期间,整个界面换上节日氛围的配色。

这种“换皮肤”的功能,在小程序里如何实现?如果为每种皮肤单独做一套页面,代码量翻倍,维护成本高得吓人。今天聊聊一套代码支持多种皮肤的技术方案。

先分清两类换肤需求

第一类是跟随系统的深色模式。用户手机开启深色模式后,小程序自动适配。这是操作系统级别的需求,微信官方提供了标准方案。

第二类是用户主动切换,比如点击“日间/夜间”按钮,或者应用内换主题色(红色版、蓝色版、节日版)。这类需要开发者自己实现。

两者的实现思路不同,但核心都是同一个问题:如何让界面颜色不写死。

方案一:官方深色模式适配

微信小程序从基础库2.11.0开始支持深色模式。适配方式有两种。

第一种是在页面JSON文件中配置。在app.json或页面的.json文件里,可以声明深色模式下的样式文件:

json
{
  "darkmode": true,
  "themeLocation": "theme.json",
  "dark": {
    "backgroundTextColor": "#ffffff",
    "navigationBarBackgroundColor": "#000000"
  }
}

第二种更常用:在CSS中使用媒体查询。在wxss文件里,根据系统主题切换颜色:

css
/* 默认浅色 */
page {
  background-color: #f5f5f5;
  color: #333333;
}

/* 深色模式覆盖 */
@media (prefers-color-scheme: dark) {
  page {
    background-color: #1a1a1a;
    color: #e0e0e0;
  }
}

这种方式的好处是系统自动判断,用户不需要手动操作。缺点是只有“深/浅”两档,无法支持自定义多色系。

方案二:CSS变量实现多主题

如果你需要支持多个预设主题,或者让用户自定义主色调,CSS变量是最干净的做法。

步骤如下:

第一步,在app.wxss中定义全局变量:

css
page {
  --primary-color: #07c160;
  --bg-color: #ffffff;
  --text-color: #333333;
  --border-color: #e5e5e5;
}

page.theme-dark {
  --primary-color: #07c160;
  --bg-color: #1a1a1a;
  --text-color: #e5e5e5;
  --border-color: #2c2c2c;
}

page.theme-red {
  --primary-color: #e64340;
  --bg-color: #ffffff;
  --text-color: #333333;
  --border-color: #e5e5e5;
}

第二步,在组件的wxss中使用这些变量:

css
.button {
  background-color: var(--primary-color);
  color: var(--text-color);
}

.container {
  background-color: var(--bg-color);
  border-color: var(--border-color);
}

第三步,在JS中切换主题类名:

javascript
// 切换到深色主题
function setDarkTheme() {
  const page = this
  page.setData({ themeClass: 'theme-dark' })
  wx.setStorageSync('userTheme', 'dark')
}

// 页面模板中动态绑定
// <view class="{{themeClass}}"> 页面内容 </view>

用户切换后,整个页面的所有使用了CSS变量的地方会自动更新,不需要重新渲染数据。

方案三:多份样式文件动态加载

如果主题之间差异很大——不仅是颜色,还包括布局、字体、组件形状——CSS变量就不够用了。这时可以考虑多份样式文件。

在项目目录下创建多个样式文件:

text
styles/
  base.wxss      (公共样式)
  light.wxss     (浅色主题)
  dark.wxss      (深色主题)
  festival.wxss  (节日主题)

在app.wxss中只引入base.wxss。然后在App的onLaunch中,根据存储的主题名称,动态加载对应的样式文件:

javascript
// 在app.js中
onLaunch() {
  const theme = wx.getStorageSync('appTheme') || 'light'
  this.loadTheme(theme)
},

loadTheme(theme) {
  const styles = {
    light: '/styles/light.wxss',
    dark: '/styles/dark.wxss',
    festival: '/styles/festival.wxss'
  }
  if (styles[theme]) {
    // 使用wx.loadFontFace类似的思路,但样式文件需要用import方式引入
    // 更简单的方法:在app.wxss中使用@import,根据不同条件动态切换
  }
}

不过这种方式有个局限:微信小程序不支持运行时动态卸载已加载的样式。更常见的做法是放弃这个方案,转而采用CSS变量配合多组class。

实际项目中的建议

如果你是个人项目或小团队项目,优先选择CSS变量方案。它足够灵活,代码干净,性能好,维护成本低。

具体实施建议:

第一,在设计阶段就定义好颜色变量。至少包括:主色调、背景色、文字色(分主次)、边框色、警示色。变量名用语义命名而非具体色值,比如--color-brand而不是--color-green

第二,把主题切换的入口放在“我的”页面或个人设置中。同时记住用户的选择,存储在本地。不要每次打开都重置。

第三,对图片资源也要处理。深色模式下,原本白色的图标可能看不清。解决方案有两种:使用SVG图标并通过CSS控制fill颜色;或者准备两套图片,根据主题切换加载不同的URL。

第四,避免在wxss中直接写死色值。养成习惯:所有颜色都用变量。后期如果需要新增主题,只需要新增一组变量定义,不需要改任何组件样式。

一个容易被忽略的问题

切换主题时,如果页面正在滚动或处于输入状态,用户不希望界面被重置。在切换主题时,应该只改变样式类,不要调用setData去重置数据,更不要重新加载页面。

正确做法是:在全局数据中存储当前主题名称,所有页面通过watchobserver监听这个值的变化,触发本页面的样式更新。这样切换主题时,用户已经输入的内容不会丢失。

回到开头的问题

一套代码支持多套皮肤,不是什么高深的技术。它的核心是:把颜色和样式参数从组件中抽离出来,变成可配置的变量。无论是系统深色模式、用户自定义主题,还是节日氛围换肤,本质上都是在改变这些变量的值。

下次你再看到一个小程序可以一键换颜色,你就知道它背后大概是怎么实现的了。

电话咨询
QQ咨询
在线咨询
服务投诉