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

共 15057字,需浏览 31分钟

 ·

2024-06-21 09:02











大厂技术  高级前端  Node进阶

点击上方 程序员成长指北,关注公众号

回复1,加入高级Node交流群




作者:evanryuu


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


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





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



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



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


VSCode配置:


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





image.png





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>




但是我们并不希望每个地方都要我们去写,因为如果我们要设置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;
}


哈!问题解决了…吗?


要知道这样写出来的类名在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这会儿有提示了!





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)',
        },


这个方案最大的缺点就是,本来只需要一个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」即可。

   “分享、点赞在看” 支持一下





浏览 125
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报