Tailwind輕松實(shí)現(xiàn)夜間模式,能跟隨系統(tǒng)又能手動(dòng)控制!
共 15057字,需瀏覽 31分鐘
·
2024-06-21 09:02
大廠(chǎng)技術(shù) 高級(jí)前端 Node進(jìn)階
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
作者:evanryuu 原文:https://juejin.cn/post/7312727134297210914
通過(guò)本文,你將會(huì)收獲到:
-
如何讓vscode不會(huì)再對(duì) @tailwind,@apply之類(lèi)的屬性報(bào)錯(cuò) -
如何讓你的應(yīng)用既能跟隨系統(tǒng)設(shè)置,又能手動(dòng)設(shè)置夜間模式
如果本文對(duì)你有所幫助,希望你能動(dòng)動(dòng)小手點(diǎn)個(gè)免費(fèi)的贊,這會(huì)讓我更有動(dòng)力進(jìn)行寫(xiě)作,謝謝你!
VSCode配置:
這一步可以讓你的 @apply 和 @tailwind 之類(lèi)的 @ 屬性都不會(huì)報(bào)錯(cuò),并有提示
-
在項(xiàng)目根目錄下新建 .vscode文件夾 -
配置settings.json
{
"css.customData": [".vscode/tailwind.json"]
}
-
配置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"
}
]
}
]
}
來(lái)源:github.com/tailwindlab…[1]
思路
方案1: 僅使用dark: 切換夜間模式
我們?cè)?tailwind 中可以很輕松地靠類(lèi)名去實(shí)現(xiàn)切換:
<div class="bg-white dark:bg-black">...</div>
但是我們并不希望每個(gè)地方都要我們?nèi)?xiě),因?yàn)槿绻覀円O(shè)置n個(gè)屬性,就要寫(xiě)2n個(gè)類(lèi)名
<div class="bg-white dark:bg-black" />
所以會(huì)超出預(yù)想的長(zhǎng)。
另外的問(wèn)題是,如果我們將來(lái)希望去實(shí)現(xiàn)主題色的話(huà),我們還要 bg-white dark:bg-black some-theme:bg-navy (假設(shè)有 some-theme: 這個(gè)東西),就更多了。
那有什么方法可以幫我們解決這個(gè)問(wèn)題呢?
方案2: 利用 @apply 自定義類(lèi)名實(shí)現(xiàn)切換
換一個(gè)思路,如果我們能有一個(gè) bg-theme 這樣的類(lèi)名,自動(dòng)幫我們做這件事,是不是就解決了呢?
我們利用 @apply 的話(huà)就可以很簡(jiǎn)單地寫(xiě)出來(lái)下面的原子CSS:
.bg-theme {
@apply bg-white dark:bg-black;
}
哈!問(wèn)題解決了…嗎?
要知道這樣寫(xiě)出來(lái)的類(lèi)名在vscode是沒(méi)有提示的,因?yàn)閠ailwind并不會(huì)自動(dòng)去找css文件然后掃描里面的看起來(lái)像原子CSS的類(lèi)名的,vscode 的tailwind插件是通過(guò) tailwind.config. 文件工作的,只有在 tailwind.config. 里面有定義的東西才會(huì)有提示。
那我們可以這樣嗎?
// tailwind.config.js
module.exports = {
...
theme: {
extend: {
colors: {
theme: 'white dark:black'
}
}
}
...
}
很遺憾,dark:畢竟是類(lèi)名而不是顏色值,因此tailwind并不支持這樣的寫(xiě)法。
但是!Tailwind是支持CSS變量的。比如你可以寫(xiě)成
// tailwind.config.js
...
colors: {
theme: 'var(--theme-color)'
}
...
有了這個(gè)功能,這里就可以引入第三個(gè)方案
方案3:媒體查詢(xún) + CSS變量
我們知道如果用戶(hù)的系統(tǒng)設(shè)置為深色模式的話(huà),我們是可以通過(guò)媒體查詢(xún)屬性 prefers-color-shceme: dark讀取到的,因此我們就能以此為根據(jù)寫(xiě)出日間/夜間模式兩套變量
:root {
--theme-color: white;
}
@media (prefers-color-scheme: dark) {
:root {
--theme-color: black;
}
}
// tailwind.config.js
colors: {
theme: 'var(--theme-color)',
},
這樣寫(xiě)出來(lái)的tailwind配置,不僅可以直接給bg / text / … 等等屬性使用,更重要的是,vscode這會(huì)兒有提示了!
這是一件非常棒的事情!并且,當(dāng)我們使用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)',
}
},
當(dāng)然,你也可以不修改顏色,而是更改類(lèi)名對(duì)應(yīng)的色值
: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)'
},
你還可以設(shè)置同名的屬性名分別給文本和背景
// tailwind.config.js
textColor: {
'theme': 'var(--theme-color-invert)'
},
backgroundColor: {
'theme': 'var(--theme-color)',
},
這個(gè)方案最大的缺點(diǎn)就是,本來(lái)只需要一個(gè)tailwind.config 的文件,現(xiàn)在還需要多個(gè) css 文件(main.css / theme-light.css / theme-dark.css / …
即使我們可以使用一個(gè)入口main.css引入其余的css,這也會(huì)導(dǎo)致項(xiàng)目中需要多一行 import 'my-tailwind.css' 之類(lèi)的文件,所以我個(gè)人認(rèn)為仍然是有優(yōu)化空間的。
方案4: 類(lèi)名 + 媒體查詢(xún) + css變量
實(shí)際上,通過(guò)前面的流程,我們已經(jīng)可以比較完美地支持系統(tǒng)的深色模式了,但是有的時(shí)候我們的用戶(hù)可能只希望在我們的網(wǎng)站上開(kāi)啟深色模式,所以我們要提供給用戶(hù)手動(dòng)切換的能力。
這個(gè)時(shí)候dark就要以類(lèi)名的形式添加在html上,而不是單純使用媒體查詢(xún)了,那我們可以大概思考一下我們的需求:
-
剛進(jìn)入頁(yè)面時(shí)的夜間模式要以用戶(hù)的設(shè)置為準(zhǔn) -
用戶(hù)可以手動(dòng)切換日間/夜間模式 -
用戶(hù)手動(dòng)切換之后,仍然可以跟隨系統(tǒng)進(jìn)行變化
由此,我們可以寫(xiě)一個(gè)hook useDark 做下面的事情:
-
獲取當(dāng)前系統(tǒng)的dark mode是否開(kāi)啟,設(shè)為 systemDark,做useState默認(rèn)值 -
const [dark, setDark] = useState<boolean>(systemDark) -
const toggleDark = () => setDark(!dark),用來(lái)手動(dòng)切換日間/夜間模式 -
監(jiān)聽(tīng) mediaQuery ,讓 dark 跟隨用戶(hù)系統(tǒng)設(shè)定變化而變化 -
聲明并暴露 dark變量 以及 toggleDark 方法
最后我們的成果如下:
該 useDark 可以參考這里[2]
使用state導(dǎo)致的問(wèn)題
如果有兩個(gè)組件A和B同時(shí)使用useDark的話(huà),那么就可能會(huì)有bug。因?yàn)榇藭r(shí)dark是組件內(nèi)部的state,這就導(dǎo)致了組件A點(diǎn)擊之后,只有組件A自己的dark變化了,組件B的dark是不會(huì)跟著變的。
如何解決這個(gè)問(wèn)題,各位自由發(fā)揮就好了??localStorage,全局狀態(tài)管理,或者別的什么方法,都可以。
方案對(duì)比
至此,我們來(lái)對(duì)比下上述的幾個(gè)方法
| 方案對(duì)比 | 僅使用 dark: 選擇器 | @apply 自定義類(lèi)名 | 媒體查詢(xún)+css變量 | 類(lèi)名+監(jiān)聽(tīng)媒體查詢(xún)+css變量 |
|---|---|---|---|---|
| 配置簡(jiǎn)單 | ?????? | ???? | ???? | ?? |
| 開(kāi)發(fā)友好 | ?? | ?? | ?????? | ?????? |
| 主題拓展 | ?? | ?? | ???? | ?????? |
| 適配性(系統(tǒng) / 用戶(hù)設(shè)置) | ???? | ???? | ???? | ?????? |
思考:如何實(shí)現(xiàn) useTheme?
我們現(xiàn)在是基于類(lèi)名實(shí)現(xiàn)深色模式的,那我們其實(shí)也很容易切換主題。比如
:root {
--theme-color: white;
}
.dark {
--theme-color: black;
}
.violet {
--theme-color: violet;
}
只要我們對(duì)useDark稍作改動(dòng),就可以變?yōu)閡seTheme了!至于如何實(shí)現(xiàn),大家也可以自己嘗試。
總結(jié)
建議使用類(lèi)名+監(jiān)聽(tīng)媒體查詢(xún)+css變量的形式,除了剛開(kāi)始的配置較為復(fù)雜,其余時(shí)候都很爽快。無(wú)論是開(kāi)發(fā)使用的方便程度還是主題的拓展性。
由于我們項(xiàng)目中接入了 next-themes ,所以也省去了自己開(kāi)發(fā) useDark ,畢竟它自帶了 useTheme 的鉤子。最后的解決方案就是
-
base.css 配置色值、字號(hào)等變量 -
theme-.css 配置各個(gè)主題下的設(shè)計(jì)類(lèi)名 -
main.css 引入 -
tailwind.config. 配置別名
如果你覺(jué)得本文有錯(cuò)誤,也歡迎你在評(píng)論區(qū)指出,我們可以友好討論??
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 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(huà)(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點(diǎn)贊、在看” 支持一下
