[velog 클론 코딩 개발기 -4 ] 다크 모드 (1)

Yeojin Choi·2022년 2월 8일
1

velog 클론코딩

목록 보기
4/5

요구 사항

  1. 처음에 사이트 방문 시 사용자 OS 설정에 해당하는 모드 적용
  2. OS에서 다크 모드 변경 시 사이트에 바로 반영
  3. 사이트 내에서 다크 모드 토글 기능
  4. 재방문시에는 사용자가 마지막으로 지정한 모드로 반영

ThemeProvider vs CSS Variables

React 기반 웹 사이트에서의 다크모드 구현 방법은 ThemeProvider 를 사용하거나, CSS Variable 를 사용하는 방법이 있다.

ThemeProvider?
App 최상위에 ThemeProvider 를 추가하면 내부 Context 를 통해 하위 컴포넌트들이 테마에 접근할 수 있도록 해준다. styled component 에서는 props.theme 를 통해, emotion.js 에서는 css prop 으로 접근할 수 있다.

CSS Variable?
CSS 사용자 지정 변수란 CSS 속성을 미리 정의해 놓고 필요할 때, "var(변수명)" 형식으로 참조하여 사용할 수 있는 변수를 의미한다.

  • CSS 속성을 미리 정의
:root {
	--bg-page1: '#F8F9FA'
}
  • 변수 참조
.content {
   background-color : var(--bg-page1);
}

구현 방법 선택의 가장 큰 기준은 SSR 이냐 CSR 이냐의 차이이다.

SSR일 경우 화면 깜박임이 발생할 수 있다.

만약 OS 설정이 다크모드인 사용자가 처음 방문한다면 라이트 모드로 보여줬다가 OS 설정을 감지하고 화면이 깜빡이면서 다크 모드로 보여질 것이다.

또한 사용자가 재방문해서 브라우저 스토리지에 저장된 사용자 테마가 있을 경우에도 테마 설정값을 가져오기 전까지 웹사이트를 표시하지 않도록 처리해야한다.

스크립트를 head 태그에 넣는다면 렌더링을 차단하면서 자바스크립트를 파싱하며 실행 딜레이가 발생할 것이고 body 태그가 끝나는 시점에 넣으면 렌더링을 차단하지는 않지만 스크립트를 실행하기 전까지는 흰 화면을 봐야한다.

테마를 설정하는 시점이 스타일시트를 불러온 후, DOM 트리가 구성되기 전이어야 한다. 그러기 위해서는 body 태그의 첫번째 child element로 script 태그를 추가해 자바스크립트 코드를 작성해야한다. (아직 SSR 이 아니라서 잘 모르겠지만 염두해 둬야 될 것같다.)

지금 진행중인 프로젝트는 CSR 에다가 ThemeProvider 를 사용하고 있었지만 추후 SSR이 될 것을 고려하여 CSS Variable 을 사용하기로 했다.

1. 처음 방문하는 사용자의 OS 설정 따라가기

1) 사용자의 OS 설정 감지하기

사용자의 시스템이 라이트 테마를 사용하는지 다크 테마를 사용하는지 알아내기 위해 prefers-color-scheme CSS 미디어 쿼리 또는 javascript 를 사용할 수 있다.

prefers-color-scheme : CSS 미디어 쿼리

  • no-preference(사용자가 시스템에 선호하는 테마를 알리지 않았음), light,dark 값이 있을 수 있다.
@media (prefers-color-scheme: light) {
  body {
    --color-text: black;
    --color-background: white; 
  }
}

@media (prefers-color-scheme: dark) {
  body {
    --color-text: white;
    --color-background: black; 
  }
}

다만 이때 시스템의 선호 테마가 없을 수도 있기 때문에 light를 기본으로 하고 dark 를 위한 미디어쿼리만 작성한다.

body {
  --color-text: black;
  --color-background: white; 
}

@media (prefers-color-scheme: dark) {
  body {
    --color-text: white;
    --color-background: black; 
  }
}


body {
  color: var(--color-text);
  background: var(--color-background);
}

2) 알아낸 OS 모드로 초기 테마 설정하기

const preferDark = systemPrefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const mode = preferDark ? 'light' : 'dark';

// body[data-theme] 속성 설정
function setTheme(theme) {
  mode = theme
  document.body.dataset.theme = theme
}
setTheme(mode)

페이지 로딩 후 시스템 테마 변경 감지

브라우저 상에서 해당 페이지가 열려있는 상태에서 시스템 테마가 변경되었다는 것을 감지해 리렌더링 되어야하기 때문에 미디어 쿼리에 대해 이벤트 리스너를 달아주어야 한다.

window.matchMedia('(prefers-color-scheme: dark)').addListener(function (e) {
  setTheme(e.matches ? 'dark' : 'light')
});

2. 다크모드 테마 스타일 셋팅

프로젝트 내에서 사용할 스타일을 emotion.js 에서 제공하는 Global Component 를 사용하여 글로벌하게 제공해 줄 수 있다.

Global Style 설정

//App.tsx
import { Global, css } from '@emotion/react'

const App = () => {
  return (
    <>
    	{/* 기존 코드 중략*/}
      <Global styles={globalStyles} />
    </>
  );
};

//globalStyles.ts

const globalStyles: Interpolation<Theme> = css`
  body {
    --bg-page1: '#F8F9FA',
  }

  @media (prefers-color-scheme: dark) {
    body {
      --bg-page1: '#121212',
    }
  }

  body[data-theme='light'] {
    --bg-page1: '#F8F9FA'
  }

  body[data-theme='dark'] {
    --bg-page1: '#121212',
  }
`;

객체 {bg_page1: '#F8F9FA'} 를 선언해두고 이를 가공해서 css variable로 정의하기 위해 --bg-page1:'#F8F9FA' 인 구조와, 컴포넌트 내에서 theme.bg_page1 처럼 참조해서 사용하기 위해 theme.bg_page1var(--bg-page1) 을 참조할 수 있는 구조로 만들어 두면 편할 것 같다.

다크모드 테마 상태 관리, 변경 및 유지, 새로 고침시에도 테마를 유지하는 내용은 다음 블로그에 계속..

profile
프론트가 좋아요

0개의 댓글