H5实现app的深色模式

175 阅读3分钟

需求概述:

webveiw的H5页面需配合app实现深色模式,即当app切换皮肤模式时,h5页面也会变化成相应的皮肤模式。

思路:

  1. app告知h5当前app的皮肤模式。
  2. h5使用标记来记录皮肤模式。
  3. h5实现两种皮肤模式的样式,并根据皮肤模式的标记来采用哪种样式。

实现历程

思路其实挺简单的,但是因为公司之前没有相应的项目可以借鉴,实现过程中还是遇到了坑。

思路步骤一实现

第一条,app如何告知h5当前页面的皮肤模式呢?第一时间想到的是使用桥接(dsBridge)来通信,一开始也是这么做的,但是这样做一直会存在一个问题。webview加载url时会首先命中默认的皮肤模式(白色),造成当前皮肤模式如果是黑色的话,加载url会出现页面闪白的情况。之后一直在优化这个问题。但是想想如果采用桥接的方式来实现app告知h5页面皮肤状态的话无法解决页面闪白的问题。页面闪白的原因之一是app与h5通信的耗时,而这个时间是不可能不存在的,所以闪白的情况无解。

既然上面的思路不可行,那么只能换一种方式拿到当前app的皮肤模式,那该怎么获取这个状态呢?webview初始化时有一个user-agent,这个字段的值是在webview加载url之前就已经存在的,所以可以通过app在user-agent后面拼接参数来表示当前app的皮肤模式。(eg: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 theme=dark')。通过在user-agent后方加上theme=dark,theme=light来代表黑色和白色皮肤。而这个字段在浏览器是可以使用window.navigator.userAgent来获取到它的值。再通过正则表达式来匹配内容就可以知道当前app的皮肤模式。

思路步骤二实现

由于页面实现是使用的Vue。要标记皮肤的模式,最好是在顶层的标签做标记,这样我们页面跳转也不用重新设置标记。那就可以选择Vue实例的挂载点(#app)、bodyhtml。设置方法就是在标签内加上属性data-theme=dark来表示黑色皮肤,data-theme=light来表示白色皮肤。

// 判断当前的皮肤模式
export function getSystemTheme() {
  const lightReg = /\?theme=light/;
  const darkReg = /\?theme=dark/;
  const u = navigator.userAgent;
  const light = !!u.match(lightReg);
  const dark = !!u.match(darkReg);

  if (light) {
    return 'light';
  }
  if (dark) {
    return 'dark';
  }
  return '';
}

// 设置标记,保存当前皮肤模式 App.vue
 beforeCreate() {
  const theme = getSystemTheme();
  if (theme === 'dark') {
    document.body.setAttribute('data-theme', 'dark');
  }
}

思路步骤三实现

因为使用了属性来标记皮肤的模式,所以可以使用属性选择器来实现黑色皮肤的样式。这是使用scss的mixin。

$light-colors: (
  white: #ffffff,
  gapLine: #ECECEC,
  border: #DDDDDD,
  ground: #F3F3F3,
);

$dark-colors: (
  white: #161A22,
  gapLine: #2D2F38,
  border: #31394D,
  ground: #0D1114,
);

@mixin bg-color($k, $important: false) {
  @if ($important) {
    [data-theme="dark"] & {
      background-color: map-get($map: $dark-colors, $key: $k) !important;
    }    
  } @else {
    [data-theme="dark"] & {
      background-color: map-get($map: $dark-colors, $key: $k);
    }
  }
}

// 使用
.menu {
    background: map-get($light-colors, white);
    @include bg-color(white);
}

// 上面四行将转换成 =>
.menu {
  background: #ffffff;
}

[data-theme="dark"] .menu {
  background-color: #161A22;
}