Tailwind轻松实现夜间模式,能跟随系统又能手动控制!

大厂技术  高级前端  Node进阶
点击上方 程序员成长指北,关注公众号
回复1,加入高级Node交流群

作者:evanryuu

原文:https://juejin.cn/post/7312727134297210914

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

  1. 如何让vscode不会再对 @tailwind , @apply 之类的属性报错

  2. 如何让你的应用既能跟随系统设置,又能手动设置夜间模式

71508a631538bc246b2b9e7b77c5d082.png

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

VSCode配置:

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

1db1036e9d51c67f0e778cb3125f130d.jpeg
image.png
6ce855d28553d89f5d566aec02f21142.jpeg
image.png
  1. 在项目根目录下新建 .vscode 文件夹

  2. 配置settings.json

{
      "css.customData": [".vscode/tailwind.json"]
    }
  1. 配置tailwind.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]

思路

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

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

<div class="bg-white dark:bg-black">...</div>

6164c7cb30a0296f64353e4c5ce6441f.png

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

<div class="bg-white dark:bg-black" />

所以会超出预想的长。

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

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

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

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

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

.bg-theme {
  @apply bg-white dark:bg-black;
}

2f02c3cf4eecea90e604e550d95058b6.png

哈!问题解决了…吗?

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

那我们可以这样吗?

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

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

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

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

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

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

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

:root {
  --theme-color: white;
}

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

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

d6ea1c6786d121168358b278eddb7561.jpeg
Kapture 2023-12-16 at 18.57.21.gif

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

:root {
  --blue-400: #4489f6;
  --blue-500: #0070f3;
}

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

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

:root {
      --theme-blue: #4489f6;
    }

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

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

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

7f8501db2aa98b20b76803063a12b1f8.png

这个方案最大的缺点就是,本来只需要一个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 可以参考这里[2]

使用state导致的问题

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

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

方案对比

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

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

思考:如何实现 useTheme?

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

: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]

https://github.com/tailwindlabs/tailwindcss/discussions/5258#discussioncomment-1979394: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Ftailwindlabs%2Ftailwindcss%2Fdiscussions%2F5258%23discussioncomment-1979394

[2]

https://github.com/evanryuu/tw-dark-usecase/blob/main/src/hooks/useDark.ts: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fevanryuu%2Ftw-dark-usecase%2Fblob%2Fmain%2Fsrc%2Fhooks%2FuseDark.ts

Node 社群

 
 

我组建了一个氛围特别好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你对Node.js学习感兴趣的话(后续有计划也可以),我们可以一起进行Node.js相关的交流、学习、共建。下方加 考拉 好友回复「Node」即可。

6815e41de7d86650d9d8280f501f4160.png

“分享、点赞、在看” 支持一下
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值