react-vant 实现黑暗模式

825 阅读3分钟

本文介绍下如何简单快速的使用 react vant 实现黑夜模式。使用的是覆盖 root 变量,后面会介绍为什么不选择使用 ConfigProvider。

使用 root css 变量实现黑夜模式

做起来很简单,首先,我们在根组件里拿到是否为黑暗模式的变量(我这里是 darkTheme),并将其挂载到 html 节点的 data-theme 属性上。

const MyApp = function (props) {
    const { darkTheme } = useUserProfile()

    useEffect(() => {
        // 注意这里
        document.documentElement.setAttribute('data-theme', darkTheme ? 'dark' : '')
    }, [darkTheme])

    return (
        <div> ... </div>
    )
}

然后在全局 css 样式里配置如下内容:

/* 黑夜模式的变量配置 */
:root[data-theme="dark"] {
  --rv-white: #1E293B;
  --rv-black: #9CA3AF;
  --rv-blue: #8589ff;
  --rv-gray-1: #0F172A;
  --rv-gray-2: #3C4A60;
  --rv-gray-7: var(--rv-black);
}

这样,当 darkTheme 为 true 时,css 里配置的黑夜模式变量就会覆盖原先的 root 变量。

坑:不要在 dark class 样式类下覆盖 root 变量

有些同学可能会习惯性的这么写:

const MyApp = function (props) {
    const { darkTheme } = useUserProfile()

    // 根据 dark 类判断是否为黑夜模式
    return (
        <div className={darkTheme ? 'dark' : ''}> ... </div>
    )
}

然后在 .dark 样式里写黑夜变量:

.dark {
  --rv-white: #1E293B;
  --rv-black: #9CA3AF;
  /* ... */
}

然后就会出现下面这个诡异的 bug,可以看到从 devtool 里链接过去的 --rv-white 确实是黑色,但是引用这个变量的 css 样式却仍然是白色。

问题截图.gif

导致这个问题的原因是 --rv-cell-background-color 的层级(:root)比 .dark 样式类的层级要高,就导致 --rv-cell-background-color 在向上寻找 --rv-white 的值时没有找到我们定义的 .dark 样式变量,而是找到了和他同级的 --rv-white: #fff

image.png

所以说,如果想用 .dark 类的话,需要和上文中提到的一样,通过给 html 根节点来设置样式类来实现。

坑2:DatePicker 的渐变遮罩修改

在完成了大部分黑暗模式适配时,你可能会发现你的 DatePicker 变成这样子了:

image.png

打开 devtool 一看,发现它的遮罩是直接写死的,不能通过 css 变量配置:

image.png

要修改起来其实也不难,跟其他黑暗模式的代码写一起就可以了:

:root[data-theme="dark"] .rv-picker__mask {
  background-image:
    linear-gradient(180deg, hsl(217deg 33% 17% / 90%), hsl(217deg 33% 17% / 40%)),
    linear-gradient(0deg, hsl(217deg 33% 17% / 90%), hsl(217deg 33% 17% / 40%));
}

再看下,恢复正常:

image.png

为什么不用 ConfigProvider

至于为什么不适用 vant 提供的 ConfigProvider 来设置样式,有这么两个原因:

  • 配置数量过多

官方文档中提到 ConfigProvider 只能用来修改组件变量。

image.png

所以说如果用这种方法的话,要配置的变量就会很多,下面是我曾经进行的修改,注意,这些也只修改了一半左右,和上面使用 root 变量覆盖只需要五六行就完成了所有修改简直一个天上一个地下:

image.png

  • 需要适配弹出框节点

react 应用一般都挂载到 body 元素下的一个 div 里,而应用里的弹出组件,例如 DialogActionSheetPopup 等同样是挂载到 body 下的,和我们的 app 根节点同级,这就导致了这些弹出组件实际上不是 ConfigProvider 的子节点。

也就是说,我们需要找到应用里每一个弹出组件,给它设置好 teleport 才行。下面是官方文档的说明:

image.png

如果你的应用已经积累了很多代码了的话,这么搞会是非常痛苦的一件事。

tailwind 适配

如果你项目里使用了 tailwind 的话,可以用下面这种方式来复用刚刚我们在 react-vant 的实现的黑暗模式配置,在 tailwind.config.js 里的 theme.extend.colors 里引用对应的颜色即可:

module.exports = {
  content: [
    // ...
  ],
  theme: {
    extend: {
      colors: {
        mainColor: "var(--rv-black)",
        cardBackground: "var(--rv-white)",
        background: "var(--rv-gray-1)"
      },
    },
  },
  // ...
}

注意,要写在 extend 里,如果你直接写在 theme.colors 里会发现其他的 color 都无效了

之后当作类名用即可:

<div className="text-mainColor bg-cardBackground"></div>