Next.js 實(shí)戰(zhàn) (三):優(yōu)雅的實(shí)現(xiàn)暗黑主題模式

前言

Next.js 中要實(shí)現(xiàn)暗黑模式螟炫,需要用到一個庫:next-themes,它可以幫助我們很輕易地實(shí)現(xiàn)暗黑模式切換。

具體步驟

  1. 安裝 next-themes 依賴:
pnpm add next-themes
  1. 新增 /components/ThemeProvider/index.tsx 文件:
'use client';

import { ThemeProvider as NextThemesProvider } from 'next-themes';
import * as React from 'react';

export default function ThemeProvider({ children, ...props }: React.ComponentProps<typeof NextThemesProvider>) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
  1. /app/layout.tsx 文件中注入 ThemeProvider :
import { ThemeProvider } from "@/components/theme-provider"

export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <>
      <html lang="en" suppressHydrationWarning>
        <head />
        <body>
          <ThemeProvider
            attribute="class"
            defaultTheme="system"
            enableSystem
            disableTransitionOnChange
          >
            {children}
          </ThemeProvider>
        </body>
      </html>
    </>
  )
}
  1. 新增 /components/ThemeModeButton/index.tsx 主題切換組件:
'use client';

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';

import { Button } from '@/components/ui/button';

export default function ThemeModeButton() {
  const { theme, setTheme } = useTheme();

  return (
    <Button variant="ghost" size="icon" onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  );
}

過渡動畫

  1. 如果你想加入過渡動畫免都,可以把代碼改成這樣:
'use client';

import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';

import { Button } from '@/components/ui/button';

export default function ThemeModeButton() {
  const { theme, setTheme } = useTheme();

  // 判斷是否支持 startViewTransition API
  const enableTransitions = () =>
    'startViewTransition' in document && window.matchMedia('(prefers-reduced-motion: no-preference)').matches;

  // 切換動畫
  async function toggleDark({ clientX: x, clientY: y }: MouseEvent) {
    const isDark = theme === 'dark';

    if (!enableTransitions()) {
      setTheme(theme === 'light' ? 'dark' : 'light');
      return;
    }

    const clipPath = [
      `circle(0px at ${x}px ${y}px)`,
      `circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`,
    ];

    await document.startViewTransition(async () => {
      setTheme(theme === 'light' ? 'dark' : 'light');
    }).ready;

    document.documentElement.animate(
      { clipPath: !isDark ? clipPath.reverse() : clipPath },
      {
        duration: 300,
        easing: 'ease-in',
        pseudoElement: `::view-transition-${!isDark ? 'old' : 'new'}(root)`,
      },
    );
  }

  return (
    <Button variant="ghost" size="icon" onClick={toggleDark}>
      <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </Button>
  );
}
  1. /app/glocals.css 文件中加入過渡樣式:
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(root),
.dark::view-transition-new(root) {
  z-index: 1;
}

::view-transition-new(root),
.dark::view-transition-old(root) {
  z-index: 9999;
}

使用方法

在需要的位置引入組件:

import ThemeModeButton from '@/components/ThemeModeButton';

<ThemeModeButton />

最終效果

c2h2e0jn7asoh7nfc62zgnkm5g5q4cqh.gif
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市件甥,隨后出現(xiàn)的幾起案子藏研,更是在濱河造成了極大的恐慌,老刑警劉巖咐汞,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盖呼,死亡現(xiàn)場離奇詭異,居然都是意外死亡化撕,警方通過查閱死者的電腦和手機(jī)几晤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侯谁,“玉大人锌仅,你說我怎么就攤上這事∏郊” “怎么了热芹?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惨撇。 經(jīng)常有香客問我伊脓,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任报腔,我火速辦了婚禮株搔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纯蛾。我一直安慰自己纤房,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布翻诉。 她就那樣靜靜地躺著炮姨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碰煌。 梳的紋絲不亂的頭發(fā)上舒岸,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音芦圾,去河邊找鬼蛾派。 笑死,一個胖子當(dāng)著我的面吹牛个少,可吹牛的內(nèi)容都是我干的洪乍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼夜焦,長吁一口氣:“原來是場噩夢啊……” “哼典尾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起糊探,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤钾埂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后科平,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體褥紫,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年瞪慧,在試婚紗的時候發(fā)現(xiàn)自己被綠了髓考。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弃酌,死狀恐怖氨菇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情妓湘,我是刑警寧澤查蓉,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站榜贴,受9級特大地震影響豌研,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一鹃共、第九天 我趴在偏房一處隱蔽的房頂上張望鬼佣。 院中可真熱鬧,春花似錦霜浴、人聲如沸晶衷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽房铭。三九已至,卻和暖如春温眉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翁狐。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工类溢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人露懒。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓闯冷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親懈词。 傳聞我的和親對象是個殘疾皇子蛇耀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容