Emotion으로 다크모드 적용해보았다.
theme 색상적용
먼저 styles 폴더 하위에 theme 전용 파일인 Theme.ts를 만들고 라이트 / 다크 모드에 대한 색상 값을 지정해줬다.
// styles/Theme.ts
import { Theme } from "@emotion/react";
interface VariantType {
[key: string]: string;
}
export const colors: VariantType = {
white: "#fff",
};
export const lightTheme: Theme = {
mode: {
text: "#202020",
background: "#F5F7FC",
backgroundMain: colors.white,
borderColor: "rgba(17, 17, 17, 0.15)",
},
};
export const darkTheme: Theme = {
mode: {
text: "#D9D9D9",
background: "#32323D",
backgroundMain: "#3f3f4d",
borderColor: "#666565",
},
};
ThemeProvider
그 다음으로 theme 을 사용하기 위해 ThemeProvide r를 페이지에 적용해야 하는데 최 상단인 _app.tsx에서 <Component {...pageProps} />를 <Layout></Layout>로 감싸는 방식으로 진행했기 때문에 Layout compoenet에서 ThemeProvider를 적용했다.
Layout 전체를 ThemeProvider 로 감싸고 styles 폴더에서 라이트 / 다크 모드에 대한 색상 값을 지정한 lightTheme / darkTheme를 colorTheme 상태 값에 알맞게 지정해줬다.
theme={colorTheme === lightTheme ? lightTheme : darkTheme}
Header에서 라이트 / 다크 모드를 변경할 버튼을 배치 할 예정이기 때문에 Header component 에 colorTheme={colorTheme} setColorTheme={setColorTheme} 값을 넘겼다.
// components/Layout.tsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { PropsWithChildren } from "react";
import Header from "./Header";
import Nav from "./Navbar";
import { ThemeProvider } from "@emotion/react";
import { darkTheme, lightTheme } from "@/styles/Theme";
import { useState } from "react";
export default function Layout({ children }: PropsWithChildren) {
const [colorTheme, setColorTheme] = useState(lightTheme);
return (
<ThemeProvider theme={colorTheme === lightTheme ? lightTheme : darkTheme}>
<div css={layout}>
<Header colorTheme={colorTheme} setColorTheme={setColorTheme} />
<section className="bottom-area">
<Nav />
<div className="right-area">
<section className="content-container">{children}</section>
</div>
</section>
</div>
</ThemeProvider>
);
}
라이트 / 다크 모드 버튼 & localStorage 저장
먼저 styles/Theme에서 darkTheme, lightTheme를 가지고 온 후 colorTheme이 darkTheme / lightTheme 일 경우의 버튼 스타일을 지정해준다.
<button
onClick={toggleColorTheme}
css={css`color: ${colorTheme === lightTheme ? "#202020" : "#D9D9D9"}`}
>
{colorTheme === lightTheme ? (
<BsSun css={{ fontSize: "2.4rem" }} />
) : (
<BsMoon css={{ fontSize: "2.4rem" }} />
)}
</button>
그리고 사용자가 라이트, 다크 모드 변경 시 브라우저를 나가더라도 다시 진입 시 해당 값이 유지되기 위해서 localStorage에 colorTheme값을 저장해야 된다. setMode 함수를 만들어 Key 명이 theme,
Value 값이 mode 에 따라 light / dark 로 저장되게 하였다.
const setMode = (mode: any) => {
mode === lightTheme
? window.localStorage.setItem("theme", "light")
: window.localStorage.setItem("theme", "dark");
setColorTheme(mode);
};
이제 onClick={toggleColorTheme} 통해 colorTheme 이 변할 수 있도록 toggleColorTheme 함수를 만들고 setMode에도 Theme을 보낸다.
const toggleColorTheme = () => {
colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
colorTheme === lightTheme ? setColorTheme(darkTheme): setColorTheme(lightTheme);
};
마지막으로 useLayoutEffect를 활용하여 페이지에 진입 시 localStorage에 theme이 있다면 해당 값을 colorTheme에 적용한다.
useLayoutEffect(() => {
const localTheme = window.localStorage.getItem("theme");
if (localTheme !== null) {
if (localTheme === "dark") {
setColorTheme(darkTheme);
} else {
setColorTheme(lightTheme);
}
}
}, [setColorTheme]);
useLayoutEffect 를 사용한 이유
새로 고침 시 화면 깜빡임 문제를 해결하기 위함이다.
useEffect와 useLayoutEffect의 차이점
- useEffect는 DOM이 화면에 그려진 이후에 호출됩니다.그렇기 때문에 useEffect를 이용해서 Layout을 변경할 경우, 새로 고침 시 화면이 깜빡이는 문제가 발생할 수 있습니다. 이전 상태가 그려진 이후에, Layout이 바뀐다.
- useLayoutEffect는 이러한 문제를 해결하기 위해 등장했습니다. useLayoutEffect는 DOM이 화면에 그려지기 전에 호출됩니다. 따라서, useLayoutEffect를 이용해 Layout을 변경할 경우, Layout이 변경된 이후에 DOM이 그려지기 때문에 새로 고침 시 깜빡임이 발생하지 않습니다.
전체 코드
// components/Header.tsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";
import { useLayoutEffect, useState } from "react";
import { darkTheme, lightTheme } from "@/styles/Theme";
import { BsSun, BsMoon } from "react-icons/bs";
export default function Header({ colorTheme, setColorTheme }: any) {
// darkmode save in localStorage
const setMode = (mode: any) => {
mode === lightTheme
? window.localStorage.setItem("theme", "light")
: window.localStorage.setItem("theme", "dark");
setColorTheme(mode);
};
const toggleColorTheme = () => {
colorTheme === lightTheme ? setMode(darkTheme) : setMode(lightTheme);
colorTheme === lightTheme
? setColorTheme(darkTheme)
: setColorTheme(lightTheme);
};
useLayoutEffect(() => {
const localTheme = window.localStorage.getItem("theme");
if (localTheme !== null) {
if (localTheme === "dark") {
setColorTheme(darkTheme);
} else {
setColorTheme(lightTheme);
}
}
}, [setColorTheme]);
return (
<header css={headerArea}>
<h3 css={{ fontSize: "1.8rem", minWidth: "22rem" }}>
사이트
</h3>
<div className="header-right">
<button
onClick={toggleColorTheme}
css={css`color: ${colorTheme === lightTheme ? "#202020" : "#D9D9D9"}`}
>
{colorTheme === lightTheme ? (
<BsSun css={{ fontSize: "2.4rem" }} />
) : (
<BsMoon css={{ fontSize: "2.4rem" }} />
)}
</button>
</div>
</header>
);
}
const headerArea = (theme: ThemeType) => css`
display: flex;
justify-content: space-between;
width: 100%;
height: 6rem;
align-items: center;
padding: 0 2rem;
border-bottom: 1px solid ${theme.mode.borderColor};
`;
css에서 theme 적용 방법
css 에서는 const reset = (theme: ThemeType) => css``` 이렇게 theme을 가지고 와서 theme 에 있는 mode에 따라 선언된 값을 가지고 오면 된다. background: ${theme.mode.background};` 이렇게 선언 시 라이트 모드에서 background 색상은 "#F5F7FC”, 다크 모드에서는 "#32323D”가 들어가진다.
import { css } from "@emotion/react";
import { colors } from "./Theme";
import { ThemeType } from "@/InterfaceGather";
export const reset = (theme: ThemeType) => css`
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Pretendard";
font-weight: 400;
}
html {
font-size: 62.5%;
}
body {
width: 100%;
height: 100%;
font-size: 1.4rem;
color: ${theme.mode.text};
background: ${theme.mode.background};
}
input,
select {
border: 1px solid ${theme.mode.borderColor};
color: ${theme.mode.text};
background: ${theme.mode.backgroundMain};
}
`;
Typescript 사용 시 theme 타입 지정
Typescript 사용 시 ****theme 타입 지정이 필요해 Interface 파일을 만들어 theme 색상에 대한 타입을 지정 한 후 Theme이라는 Emotion의 기본 테마에 대한 TypeScript 인터페이스를 확장했다.
//Interface.ts
// Theme
export interface ThemeType {
mode: {
text: string;
background: string;
backgroundMain: string;
borderColor: string;
};
}
// emotion.d.ts
declare module "@emotion/react" {
export interface Theme extends ThemeType {}
}
'Css' 카테고리의 다른 글
다크모드 적용 (next-themes) (0) | 2024.08.23 |
---|---|
svg 색상 변경 (0) | 2024.08.23 |
텍스트 라인 수 제한 (0) | 2024.08.23 |
tailwind 변수 값 적용 (0) | 2024.08.23 |
tailwind 폰트 적용 (0) | 2024.08.23 |