Next.js 实战 (三):优雅的实现暗黑主题模式

简介: 这篇文章介绍了在Next.js中实现暗黑模式的具体步骤。首先,需要安装next-themes库。然后,在/components/ThemeProvider/index.tsx文件中新增ThemeProvider组件,并在/app/layout.tsx文件中注入该组件。如果想要加入过渡动画,可以修改代码实现主题切换时的动画效果。最后,需要在需要的位置引入ThemeModeButton组件,实现暗黑模式的切换。

前言

Next.js 中要实现暗黑模式,需要用到一个库:next-themes,它可以帮助我们很轻易地实现暗黑模式切换。

具体步骤

1.安装 next-themes 依赖:

pnpm add next-themes

2.新增 /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>;
}

3./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>
    </>
  )
}

4.新增 /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>
  );
}

2./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

相关文章
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理与实战
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理与实战
|
15天前
Next.js 实战 (二):搭建 Layouts 基础排版布局
本文介绍了作者在Next.js v15.x版本发布后,对一个旧项目的重构过程。文章详细说明了项目开发规范配置、UI组件库选择(最终选择了Ant-Design)、以及使用Ant Design的Layout组件实现中后台布局的方法。文末展示了布局的初步效果,并提供了GitHub仓库链接供读者参考学习。
Next.js 实战 (二):搭建 Layouts 基础排版布局
|
9天前
|
存储 网络架构
Next.js 实战 (四):i18n 国际化的最优方案实践
这篇文章介绍了Next.js国际化方案,作者对比了网上常见的方案并提出了自己的需求:不破坏应用程序的目录结构和路由。文章推荐使用next-intl库来实现国际化,并提供了详细的安装步骤和代码示例。作者实现了国际化切换时不改变路由,并把当前语言的key存储到浏览器cookie中,使得刷新浏览器后语言不会失效。最后,文章总结了这种国际化方案的优势,并提供Github仓库链接供读者参考。
|
25天前
|
设计模式 前端开发 JavaScript
JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式
本文深入探讨了JavaScript设计模式及其在实战中的应用,涵盖单例、工厂、观察者、装饰器和策略模式,结合电商网站案例,展示了设计模式如何提升代码的可维护性、扩展性和可读性,强调了其在前端开发中的重要性。
29 2
|
1月前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第40天】在JavaScript的世界里,异步编程是一道不可或缺的风景线。它允许我们在等待慢速操作(如网络请求)完成时继续执行其他任务,极大地提高了程序的性能和用户体验。本文将深入浅出地探讨Promise、async/await等异步编程技术,通过生动的比喻和实际代码示例,带你领略JavaScript异步编程的魅力所在。
25 1
|
2月前
|
前端开发 JavaScript UED
探索JavaScript中的异步编程模式
【10月更文挑战第21天】在数字时代的浪潮中,JavaScript作为一门动态的、解释型的编程语言,以其卓越的灵活性和强大的功能在Web开发领域扮演着举足轻重的角色。本篇文章旨在深入探讨JavaScript中的异步编程模式,揭示其背后的原理和实践方法。通过分析回调函数、Promise对象以及async/await语法糖等关键技术点,我们将一同揭开JavaScript异步编程的神秘面纱,领略其带来的非阻塞I/O操作的魅力。让我们跟随代码的步伐,开启一场关于时间、性能与用户体验的奇妙之旅。
|
2月前
|
JavaScript 前端开发 开发者
探索JavaScript原型链:深入理解与实战应用
【10月更文挑战第21天】探索JavaScript原型链:深入理解与实战应用
33 1
|
2月前
|
SQL 前端开发 JavaScript
Nest.js 实战 (十五):前后端分离项目部署的最佳实践
这篇文章介绍了如何使用现代前端框架Vue3和后端Node.js框架Nest.js实现的前后端分离架构的应用,并将其部署到生产环境。文章涵盖了准备阶段,包括云服务器的设置、1Panel面板的安装、数据库的安装、域名的实名认证和备案、SSL证书的申请。在部署Node服务环节,包括了Node.js环境的创建、数据库的配置、用户名和密码的设置、网站信息的填写、静态网站的部署、反向代理的配置以及可能遇到的常见问题。最后,作者总结了部署经验,并希望对读者有所帮助。
203 11
|
1月前
|
前端开发 JavaScript UED
探索JavaScript的异步编程模式
【10月更文挑战第33天】在JavaScript的世界里,异步编程是提升应用性能和用户体验的关键。本文将带你深入理解异步编程的核心概念,并展示如何在实际开发中运用这些知识来构建更流畅、响应更快的Web应用程序。从回调函数到Promises,再到async/await,我们将一步步解锁JavaScript异步编程的秘密,让你轻松应对各种复杂的异步场景。
|
2月前
|
存储 JavaScript 前端开发
前端开发:Vue.js入门与实战
【10月更文挑战第9天】前端开发:Vue.js入门与实战