1. 概要
簡単なアニメーションの挙動
https://x.com/Nao8epicmotion/status/1780411806277919134
ソースコード
https://github.com/Kroro1208/nextjs-portfolio-website.git
React/Next.jsを使用してwebsiteの開発を行う際に、よく出てくるライトモードとダークモードの切り替えを解説します。
もうこれはパターン化できるもので、一度覚えてしまえば使いまわしたりして表現できる幅も広がると思います。この際に覚えちゃいましょう。
2. 必要なコンポーネントとファイル
今回のテーマに関係ある部分だけ解説させていただきます。
iconはReact iconを使用してます。
app/layout.jsx
ここで後に作成するThemeProvider
コンポーネントでレンダリングされる部分を囲います。
import { Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { ThemeProvider } from "@/components/ThemeProvider";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={inter.className}>
<ThemeProvider attribute="class" defaultTheme="light">
<Header />
{children}
<Footer />
</ThemeProvider>
</body>
</html>
);
}
app/Home.jsx
import About from "@/components/About";
import Cta from "@/components/Cta";
import Hero from "@/components/Hero";
import Reviews from "@/components/Reviews";
import Services from "@/components/Services";
import Works from "@/components/Works";
export default function Home() {
return (
<main>
<Hero />
<About />
<Services />
<Works />
<Reviews />
<Cta />
</main>
);
}
components/Header.jsx
※ここのuseEffectはスクロールアニメーションに関係するので今回は関係ありません。
動画にあるスクールの時に、遅れてニュルっと出てくるオレンジのBarのやつです。
"use client";
import { useEffect, useState } from "react";
import Logo from "./Logo";
import MobileNav from "./MobileNav";
import Nav from "./Nav";
import ThemeToggle from "./ThemeToggle"
import { usePathname } from "next/navigation";
const Header = () => {
const [header, setHeader] = useState(false);
const pathname = usePathname();
useEffect(() => {
const scrollYPos = window.addEventListener('scroll', () => {
window.scrollY > 50 ? setHeader(true) : setHeader(false);
});
return () => window.removeEventListener('scroll', scrollYPos);
});
return (
<header className={`${header ? "py-4 bg-gray-200 shadow-lg dark:bg-accent" : "py-6 dark:bg-transparent"}
sticky top-0 z-30 transition-all ${pathname === "/" && "bg-sky-200"}`}>
<div className="container mx-auto">
<div className="flex justify-between items-center">
<Logo />
<div className="flex items-center gap-x-6">
<Nav
containerStyles="hidden xl:flex gap-x-8 items-center"
linkStyles="relative hover:text-primary transition-all"
underlineStyles="absolute left-0 top-full h-[2px] bg-primary w-full"
/>
<ThemeToggle />
<div className="xl:hidden">
<MobileNav />
</div>
</div>
</div>
</div>
</header>
)
}
export default Header
components/ThemeProvider.jsx
ここでライブラリをインポートしてNextThemeProvider
として返します。
この使い方がちょっとだけ難しいかもですが、...props
にはlayout.jsx
に記載している<ThemeProvider>
内のattribute="class" defaultTheme="light"
が渡ってきます。
この二つのpropsはThemeProvider
を使用するときの書き方みたいなものです。
"use client";
import { ThemeProvider as NextThemeProvider } from "next-themes";
export function ThemeProvider({ children, ...props }) {
return <NextThemeProvider {...props}>{children}</NextThemeProvider>;
}
components/ThemeToggle.jsx
rotateとscaleを使用してアイコンをクリックしたときに回転させてちょっとオシャレに。
ボタンにvariant="outline"を指定して、枠線だけが表示させ中身が透明な「アウトラインスタイル」を持たせてます。
"use client";
import { Button } from "./ui/button";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { useTheme } from "next-themes";
const ThemeToggle = () => {
const { theme, setTheme } = useTheme();
return (
<Button variant="outline" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")}>
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-0 scale-0 transition-all dark:rotate-0 dark:scale-100" />
</Button>
)
}
export default ThemeToggle
app/global.css
ここでデフォルトカラーとdarkモード時に必要となるカラーを定義する
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@layer base {
:root {
--background: 0 0% 100%;
--primary: 8 99% 67%;
--primary-foreground: 300 0% 100%;
--secondary: 240 19% 16%;
--secondary-foreground: 300 0% 100%;
--border: 8 82% 92%;
--input: 8 82% 92%;
--ring: 9 99% 67%;
他の設定
}
.dark {
--background: 237 22% 20%;
--primary: 8 99% 67%;
--primary-foreground: 300 0% 100%;
--secondary: 240 19% 16%;
--secondary-foreground: 300 0% 100%;
--border: 237 22% 23%;
--input: 237 22% 23%;
--ring: 8 99% 67%;
他の設定
}
}
}
tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{js,jsx}',
'./components/**/*.{js,jsx}',
'./app/**/*.{js,jsx}',
'./src/**/*.{js,jsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"sm": "640px",
"md": "768px",
"lg": "1024px",
"xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
},
},
},
plugins: [require("tailwindcss-animate")],
}
以上です!!
次回は、スクロール時のニュルっと出てくるやつを解説します!