背景

之前做过小程序适配暗夜模式的需求,分享一下经验。

因为是微信小程序(不是抖音小程序、支付宝小程序等等),产品要求我们使用微信的样式,所以我们项目采用了​​weui-wxss​​。

weui的坑

但是这个weui wxss比较坑,它里面虽然有暗夜模式的样式,但是必须在根结点增加属性 ​​data-weui-theme="dark"​​,才能生效。例如:

<view data-weui-theme="dark">

</view>

为什么说它坑?

因为我们都习惯了使用​​@media (prefers-color-scheme:dark) {}​​来判断暗夜模式/白天模式。这是原生的方法,非常方便。

但是weui wxss没用这个写法,都是以​​data-weui-theme​​属性为准。而这个属性的值,需要开发者赋值。需要专门弄一个变量作为页面的data,使用API获取当前系统主题后,动态赋值。

因为暗黑模式的实现还不得不耦合JS,就比较恶心了,暗夜模式的JS逻辑侵入了我们业务代码。

具体做法

在app.json配置darkmode

{
"darkmode": true,
"themeLocation": "theme.json"
}

还需要增加文件​​theme.json​​​,可以参考官方文档:​​DarkMode 适配指南​​。

每个页面中声明一个theme的变量

Page({
data: {
theme: 'light',
},
});

默认值用白天模式​​light​​​。另一个取值是​​drak​​。

每个页面中声明一个themeChangeCallback回调函数

Page({
themeChangeCallback({ theme }) {
this.setData({ theme });
// 这里还需要调用wx.setNavigationBarColor和wx.setBackgroundColor,设置导航栏颜色和背景色

onLoad时注册事件,监听主题色变化

Page({
onLoad(options) {
wx.onThemeChange(this.themeChangeCallback);
},
});

onUnload时取消注册事件,关闭监听主题色变化

Page({
onUnload() {
wx.offThemeChange(this.themeChangeCallback);
},
});

onShow时执行themeChangeCallback回调函数

Page({
onShow() {
this.themeChangeCallback();
},
});

在wxml中根元素设置属性

<view data-weui-theme="{{theme}}">

</view>

怎么样,上面的方案是不是要吐了?关键是:每个页面都需要写这么多重复的逻辑!可维护性极差,代码重复率极高。

我们急需一种解决方案,请往下看。

更优雅的方案

建议你先阅读下文章:​​《如何全局重写小程序 Page函数 wx对象?》​​,学会这种方法,我们再来看下方的代码。

function onLoadProxy(onLoad) {
return function (options) {
wx.onThemeChange(this.themeChangeCallback);
if (onLoad) {
return onLoad.call(this, options);
}
};
}

function onUnloadProxy(onUnload) {
return function () {
wx.offThemeChange(this.themeChangeCallback);
if (onUnload) {
return onUnload.call(this);
}
};
}

function onShowProxy(onShow) {
return function () {
this.themeChangeCallback();
if (onShow) {
return onShow.call(this);
}
};
}

const PageProxy = (Page) => function (options) {
const newOptions = {
...options,
data: { ...options.data, theme: 'light' },
themeChangeCallback({ theme }) {
this.setData({ theme });
// 这里还需要调用wx.setNavigationBarColor和wx.setBackgroundColor,设置导航栏颜色和背景色
},
onLoad: onLoadProxy(options.onLoad),
onUnload: onUnloadProxy(),
onShow: onShowProxy(),
};
Page(newOptions);
};

Page = PageProxy(Page);

我们通过全局改写Page对象,一次性改造了所有页面的​​onLoad​​​、​​onShow​​​、​​onUnload​​。之后,我们的页面的代码中,完全不用管暗夜模式相关逻辑。

这避免了暗夜模式JS对业务代码的入侵,同时实现了暗夜模式这一功能,真是太方便了~