SR에서 기획했던 Bare Minimum과 Advanced 단계의 목표 중 내가 맡은 파트를 끝내서 Nightmare 중 다크 모드 구현을 하게 되었다. 다른 목표들은 서버의 구현이 필요하거나 데이터 베이스의 스키마를 수정해야해서 클라이언트에서 구현이 가능한 다크모드를 우선 구현하였다.
유저가 다크모드로 변경하면 추후 페이지에 접속하거나 새로고침을 해도 유지가 되어야 하기 때문에 다크모드여부를 따로 저장할 수 있는 공간이 필요했다. state나 redux로 관리하면 새로고침 될때마다 초기화되기 때문에 부적합하고 처음에는 쿠키로 저장해놓을까 고민했었는데, 여러 포스팅을 찾아보면서 localStorage를 사용하는 것이 적절하다는 것을 알게 되었다.
//useTheme.js
import { useState } from 'react';
const useTheme = () => {
const prefersColorScheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
const localTheme = localStorage.getItem('theme');
const initialTheme = localTheme || prefersColorScheme;
const [theme, setTheme] = useState(initialTheme);
const setMode = (mode) => {
localStorage.setItem('theme', mode);
setTheme(mode);
};
const themeToggler = () => {
theme === 'light' ? setMode('dark') : setMode('light');
};
return [theme, themeToggler];
};
export default useTheme;
처음에는 라이트/다크 모드 여부만 localStorage에 저장하고 처음 접속한 경우 라이트 모드가 적용되도록 설정하려고 했었다. 관련된 포스트를 찾아보던 중 prefers-color-scheme
와 window.matchMedia
를 사용하여 유저의 테마를 적용할 수 있는 방법이 잘 정리되어 있는 글을 발견하여 같은 방법을 사용하였다.
참고 포스팅 : Gatsby Blog 다크 모드 적용기
function App() {
const [theme, themeToggler] = useTheme();
...
return (
<div className='App' data-theme={theme}>
...
{theme==='light' ? (
<div className='btn-theme dark' onClick={()=>{themeToggler()}}><FontAwesomeIcon className='icon-theme' icon={faMoon} /><div>다크 모드로 보기</div></div>
) : (
<div className='btn-theme light' onClick={()=>{themeToggler()}}><FontAwesomeIcon className='icon-theme' icon={faSun} /><div>라이트 모드로 보기</div></div>
)}
</div>
);
}
export default App;
App 컴포넌트에 position:fixed
인 버튼을 생성하고 useTheme의 theme에 따라 보여지는 버튼이 다르게 설정하고 버튼 클릭시 themeToggler()이 실행되어 localStorage의 theme 값이 변경되도록 설정해주었다.
최상단 태그의 속성에 data-theme={theme}
를 추가해주어 theme에 따라 적용되는 CSS가 달라질 수 있도록 설정하였다.
다크모드에 따라 CSS의 color 값이 변경되는 방법이 다양했는데 보통 styled-components를 사용한 방법이 대다수였다. 다크모드 구현을 위해서 웹페이지 전체에 styled-components를 사용하는 방식으로 변경하는 것은 무리여서 다른 방법을 찾던 중 CSS도 변수를 설정할 수 있다는 것을 알게 되었다.
/*APP.css*/
:root {
--main-color1: #39CFD9;
--main-color2: rgb(172, 172, 172);
--btn-color1: rgb(100, 100, 100);
--btn-color2: #FFF;
--nav-color: rgb(211, 211, 211);
--main-backcolor1: #fafafa;
--main-backcolor2: #f4f4f49d;
--main-backcolor3: white;
--main-backcolor4: #656565;
--font-color1: black;
--font-color2: #535353;
--font-color3: rgb(56, 56, 56);
--shadow-color: rgb(187, 187, 187);
}
[data-theme="dark"] {
--main-color1: #367c81;
--main-color2: rgb(88, 86, 86);
--btn-color1: rgb(240, 240, 240);
--btn-color2: rgb(54, 54, 54);
--nav-color: rgb(93, 93, 93);
--main-backcolor1: #202020;
--main-backcolor2: #202020;
--main-backcolor3: rgb(93, 93, 93);
--main-backcolor4: #313131;
--font-color1: white;
--font-color2: #eeeeee;
--font-color3: rgb(233, 233, 233);
--shadow-color: rgb(0, 0, 0);
}
.App {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Apple SD Gothic Neo,arial,sans-serif;
overflow-x: hidden;
background-color: var(--main-backcolor1);
color: var(--font-color1);
}
...
--
로 변수명을 설정해주고 background-color
나 color
값을 var(--변수명)
으로 설정해주면 해당하는 색상으로 적용이 가능하다. 모든 페이지에서 따로 설정할 필요없이 색상만 설정해주고 CSS 변수만 사용하면 구현이 가능하다.
이전 프로젝트부터 구현해보고 싶었던 기능인데 이번에 경험할 수 있어서 좋은 기회였다. 처음에 다크모드 구현을 생각하지 않고 메인 컬러를 제외하고 각자 color 값을 설정해서 통일시키는데 시간을 썼던것 같다. 다음 프로젝트에서는 SR 단계에서 좀 더 세부적으로 color 값을 정하고 개발을 진행하는 것도 좋을 것 같다.