TailwindCSS如何轻松实现夜间模式,既能跟随系统又能手动控制

仓库地址:github.com/evanryuu/tw... 可以直接切换到对应的commit查看各个方案

在线预览:tw-dark-usecase.vercel.app/ (可能需要魔法)

通过本文,你将会收获到:

  1. 如何让vscode不会再对 @tailwind , @apply 之类的属性报错
  2. 如何让你的应用既能跟随系统设置,又能手动设置夜间模式

VSCode配置:

这一步可以让你的 @apply@tailwind 之类的 @ 属性都不会报错,并有提示

  1. 在项目根目录下新建 .vscode 文件夹
  2. 配置settings.json
json 复制代码
    {
      "css.customData": [".vscode/tailwind.json"]
    }
  1. 配置tailwind.json
json 复制代码
    {
      "version": 1.1,
      "atDirectives": [
        {
          "name": "@tailwind",
          "description": "Use the `@tailwind` directive to insert Tailwind's `base`, `components`, `utilities` and `screens` styles into your CSS.",
          "references": [
            {
              "name": "Tailwind Documentation",
              "url": "https://tailwindcss.com/docs/functions-and-directives#tailwind"
            }
          ]
        },
        {
          "name": "@apply",
          "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you'd like to extract to a new component.",
          "references": [
            {
              "name": "Tailwind Documentation",
              "url": "https://tailwindcss.com/docs/functions-and-directives#apply"
            }
          ]
        },
        {
          "name": "@responsive",
          "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n  .alert {\n    background-color: #E53E3E;\n  }\n}\n```\n",
          "references": [
            {
              "name": "Tailwind Documentation",
              "url": "https://tailwindcss.com/docs/functions-and-directives#responsive"
            }
          ]
        },
        {
          "name": "@screen",
          "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n  /* ... */\n}\n```\n...gets transformed into this:\n```css\n@media (min-width: 640px) {\n  /* ... */\n}\n```\n",
          "references": [
            {
              "name": "Tailwind Documentation",
              "url": "https://tailwindcss.com/docs/functions-and-directives#screen"
            }
          ]
        },
        {
          "name": "@variants",
          "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n   .btn-brand {\n    background-color: #3182CE;\n  }\n}\n```\n",
          "references": [
            {
              "name": "Tailwind Documentation",
              "url": "https://tailwindcss.com/docs/functions-and-directives#variants"
            }
          ]
        }
      ]
    }

来源:github.com/tailwindlab...

思路

方案1: 仅使用dark: 切换夜间模式

我们在 tailwind 中可以很轻松地靠类名去实现切换:

ini 复制代码
<div class="bg-white dark:bg-black">...</div>

但是我们并不希望每个地方都要我们去写,因为如果我们要设置n个属性,就要写2n个类名

html 复制代码
<div class="bg-white dark:bg-black" />

所以会超出预想的长。

另外的问题是,如果我们将来希望去实现主题色的话,我们还要 bg-white dark:bg-black some-theme:bg-navy (假设有 some-theme: 这个东西),就更多了。

那有什么方法可以帮我们解决这个问题呢?

方案2: 利用 @apply 自定义类名实现切换

换一个思路,如果我们能有一个 bg-theme 这样的类名,自动帮我们做这件事,是不是就解决了呢?

我们利用 @apply 的话就可以很简单地写出来下面的原子CSS:

less 复制代码
.bg-theme {
  @apply bg-white dark:bg-black;
}

哈!问题解决了...吗?

要知道这样写出来的类名在vscode是没有提示的,因为tailwind并不会自动去找css文件然后扫描里面的看起来像原子CSS的类名的,vscode 的tailwind插件是通过 tailwind.config. 文件工作的,只有在 tailwind.config. 里面有定义的东西才会有提示。

那我们可以这样吗?

js 复制代码
    // tailwind.config.js
    module.exports = {
      ...
      theme: {
        extend: {
          colors: {
            theme: 'white dark:black'
          }
        }
      }
      ...
    }

很遗憾,dark:毕竟是类名而不是颜色值,因此tailwind并不支持这样的写法。

但是!Tailwind是支持CSS变量的。比如你可以写成

js 复制代码
    // tailwind.config.js
    ...
          colors: {
            theme: 'var(--theme-color)'
          }
    ...

有了这个功能,这里就可以引入第三个方案

方案3:媒体查询 + CSS变量

我们知道如果用户的系统设置为深色模式的话,我们是可以通过媒体查询属性 prefers-color-shceme: dark读取到的,因此我们就能以此为根据写出日间/夜间模式两套变量

css 复制代码
:root {
  --theme-color: white;
}

@media (prefers-color-scheme: dark) {
  :root {
    --theme-color: black;
  }
}
js 复制代码
    // tailwind.config.js
            colors: {
              theme: 'var(--theme-color)',
            },

这样写出来的tailwind配置,不仅可以直接给bg / text / ... 等等属性使用,更重要的是,vscode这会儿有提示了!

这是一件非常棒的事情!并且,当我们使用css变量之后,我们要切换主题也很方便。

css 复制代码
:root {
  --blue-400: #4489f6;
  --blue-500: #0070f3;
}

@media (prefers-color-scheme: dark) {
  :root {
    --blue-400: #2d79f0;
    --blue-500: #063784;
  }
}
js 复制代码
    // tailwind.config.js
            colors: {
              theme: 'var(--theme-color)',
              blue: {
                400: 'var(--blue-400)',
                500: 'var(--blue-500)',
              }
            },

当然,你也可以不修改颜色,而是更改类名对应的色值

css 复制代码
    :root {
      --theme-blue: #4489f6;
    }

    @media (prefers-color-scheme: dark) {
      :root {
        --theme-blue: #0070f3;  
      }
    }
js 复制代码
    // tailwind.config.js
            colors: {
              theme: 'var(--theme-color)',
              'theme-blue': 'var(--theme-blue)'
            },

你还可以设置同名的属性名分别给文本和背景

js 复制代码
// tailwind.config.js
        textColor: {
          'theme': 'var(--theme-color-invert)'
        },
        backgroundColor: {
          'theme': 'var(--theme-color)',
        },

这个方案最大的缺点就是,本来只需要一个tailwind.config 的文件,现在还需要多个 css 文件(main.css / theme-light.css / theme-dark.css / ...

即使我们可以使用一个入口main.css引入其余的css,这也会导致项目中需要多一行 import 'my-tailwind.css' 之类的文件,所以我个人认为仍然是有优化空间的。

方案4: 类名 + 媒体查询 + css变量

实际上,通过前面的流程,我们已经可以比较完美地支持系统的深色模式了,但是有的时候我们的用户可能只希望在我们的网站上开启深色模式,所以我们要提供给用户手动切换的能力。

这个时候dark就要以类名的形式添加在html上,而不是单纯使用媒体查询了,那我们可以大概思考一下我们的需求:

  1. 刚进入页面时的夜间模式要以用户的设置为准
  2. 用户可以手动切换日间/夜间模式
  3. 用户手动切换之后,仍然可以跟随系统进行变化

由此,我们可以写一个hook useDark 做下面的事情:

  1. 获取当前系统的dark mode是否开启,设为 systemDark,做useState默认值
  2. const [dark, setDark] = useState<boolean>(systemDark)
  3. const toggleDark = () => setDark(!dark) ,用来手动切换日间/夜间模式
  4. 监听 mediaQuery ,让 dark 跟随用户系统设定变化而变化
  5. 声明并暴露 dark变量 以及 toggleDark 方法

最后我们的成果如下:

useDark 可以参考这里

使用state导致的问题

如果有两个组件A和B同时使用useDark的话,那么就可能会有bug。因为此时dark是组件内部的state,这就导致了组件A点击之后,只有组件A自己的dark变化了,组件B的dark是不会跟着变的。

如何解决这个问题,各位自由发挥就好了😄localStorage,全局状态管理,或者别的什么方法,都可以。

方案对比

至此,我们来对比下上述的几个方法

方案对比 仅使用 dark: 选择器 @apply 自定义类名 媒体查询+css变量 类名+监听媒体查询+css变量
配置简单 🌟🌟🌟 🌟🌟 🌟🌟 🌟
开发友好 🌟 🌟 🌟🌟🌟 🌟🌟🌟
主题拓展 🌟 🌟 🌟🌟 🌟🌟🌟
适配性(系统 / 用户设置) 🌟🌟 🌟🌟 🌟🌟 🌟🌟🌟

思考:如何实现 useTheme?

我们现在是基于类名实现深色模式的,那我们其实也很容易切换主题。比如

css 复制代码
:root {
  --theme-color: white;
}

.dark {
  --theme-color: black;
}

.violet {
  --theme-color: violet;
}

只要我们对useDark稍作改动,就可以变为useTheme了!至于如何实现,大家也可以自己尝试。

总结

建议使用类名+监听媒体查询+css变量的形式,除了刚开始的配置较为复杂,其余时候都很爽快。无论是开发使用的方便程度还是主题的拓展性。

由于我们项目中接入了 next-themes ,所以也省去了自己开发 useDark ,毕竟它自带了 useTheme 的钩子。最后的解决方案就是

  1. base.css 配置色值、字号等变量
  2. theme-.css 配置各个主题下的设计类名
  3. main.css 引入
  4. tailwind.config. 配置别名

如果本文对你有所帮助,希望你能动动小手点个免费的赞,这会让我更有动力进行写作,谢谢你!

如果你觉得本文有错误,也欢迎你在评论区指出,我们可以友好讨论😄

相关推荐
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
GISer_Jing1 小时前
React核心功能详解(一)
前端·react.js·前端框架
FØund4044 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
酷酷的威朗普4 小时前
医院绩效考核系统
javascript·css·vue.js·typescript·node.js·echarts·html5
渊兮兮4 小时前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
疯狂的沙粒5 小时前
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
react.js·typescript
土豆湿5 小时前
拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流
开发语言·javascript·css
鑫宝Code5 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
鸽鸽程序猿7 小时前
【前端】CSS
前端·css
沉默璇年14 小时前
react中useMemo的使用场景
前端·react.js·前端框架