[JavaScript] 다크모드 적용하기

정다은·2022년 10월 23일
2
post-thumbnail

📌 참고
이 게시글은 [프로그래머스] 과제테스트 연습 - 고양이 사진 검색 사이트를 풀어보며 공부한 내용의 일부를 정리하고 있습니다 :)

🤔 사담

바닐라 자바스크립트 기초에 대한 중요성을 계속해서 깨닫고 있는 요즘..
알고리즘 코딩테스트만 주구장창 준비했었지만
과제 코딩테스트도 슬슬 준비해야겠다는 생각이 들었다
대체로 바닐라 자스로만 푸는 과제코테가 많기 때문에..! 많은 공부가 될 것 같다
아직 리액트 없이 쌩 자스로 개발할 때는.. 많이 뚝딱거리지만 😥 화이팅!


💡 요구사항

  • 다크 모드(Dark mode)를 지원하도록 CSS를 수정해야 합니다.
    • CSS 파일 내의 다크 모드 관련 주석을 제거한 뒤 구현합니다.
    • 모든 글자 색상은 #FFFFFF , 배경 색상은 #000000 로 한정합니다.
    • 기본적으로는 OS의 다크모드의 활성화 여부를 기반으로 동작하게 하되, 유저가 테마를 토글링 할 수 있도록 좌측 상단에 해당 기능을 토글하는 체크박스를 만듭니다.

✅ prefers-color-scheme

[참고] prefers-color-scheme - CSS: Cascading Style Sheets | MDN

  • 사용자 OS의 다크모드/라이트모드 활성화 여부를 탐지하여 알려주는 미디어쿼리
@media (prefers-color-scheme: dark) {
  /* 다크모드에서 적용할 CSS 정의*/
}

@media (prefers-color-scheme: light) {
  /* 라이트모드에서 적용할 CSS 정의*/
}
  • :root 가상선택자를 활용하면 모드에 따라 여러 element에 적용되어야 하는 CSS variable을 한꺼번에 변경할 수도 있음
.header {
	background-color: var(--background-color);
    color: var(--font-color);
}

@media (prefers-color-scheme: dark) {
	:root {
    	--background-color: #000;
        --font-color: white;
    }
}

✅ window.matchMedia()

[참고] Window.matchMedia() - Web API | MDN

  • 주어진 미디어 쿼리 문자열의 분석 결과를 나타내는 MediaQueryList 객체를 반환
if (window.matchMedia("(min-width: 400px)").matches) {
  /* 뷰포트 너비가 400px 이상일 때 실행할 코드 작성 */
}
  • MediaQueryList 객체의 속성

    • matches : document가 현재 미디어 쿼리 문자열과 일치하면 true, 일치하지 않으면 false를 반환하는 boolean 값
    • media : 미디어 쿼리 문자열
  • prefers-color-scheme 미디어 쿼리를 분석하여 사용자의 OS가 사용 중인 모드를 탐지하는데 활용

if (window.matchMedia("(prefers-color-scheme: dark")).matches) {
  /* 다크모드일 경우 실행할 코드 작성 */
}
else {
  /* 라이트모드일 경우 실행할 코드 작성 */
}

✅ HTMLElement.dataset

[참고] HTMLElement.dataset - Web API | MDN
[참고] HTML 데이터셋(Dataset, data-*) 속성의 이해

  • HTML에 커스텀 속성을 표시하는데 표준화된 방법을 제공
  • data-*와 같이 표기하고 접근
  • 속성 추가 방법
    • HTMLElement.dataset.속성명 = 속성값;
    • HTMLElement.setAttribute("data-속성명", "속성값");
  • 속성 접근 방법
    • 속성이 DOM에 저장될 때 속성이름의 "data-" 부분은 삭제되고 뒷부분만 속성명으로 사용됨
    • HTMLElement.dataset.속성명
    • HTMLElement.getAttribute("data-속성명")
  • CSS에서의 활용
    • CSS 속성 선택자를 활용하여 dataset 속성을 선택할 수 있음
    • ex. body[data-theme="light"] { /* 라이트모드 시 실행 코드 */ }

🔽 구현 코드 (JavaScript)

부가 설명은 코드 주석을 참고해주세요 🙌

/* style.css */
@media (prefers-color-scheme: dark) {
  body {
    background-color: #000;
    color: white;
  }
}

@media (prefers-color-scheme: light) {
  body {
    background-color: white;
    color: #000;
  }
}

body[data-theme="dark"] {
  background-color: #000;
  color: white;
}

body[data-theme="light"] {
  background-color: white;
  color: #000;
}
/* Toggle.js */
class Toggle {
    constructor({ $target }) {
        // 토글 버튼의 템플릿이 될 div를 하나 생성
        this.$toggle = document.createElement("div");
        this.$toggle.style.cssText = "width: 150px; padding: 5px 0; margin: 10px 0; border: 1px solid gray; border-radius: 8px; text-align: center;"
        this.$toggle.addEventListener("mouseover", () => {
            this.$toggle.style.cursor = "pointer";
        });

        // 다크모드일 경우 '라이트모드로 보기', 라이트모드일 경우 '다크모드로 보기' 라고 보여줄 label을 하나 생성
        this.$label = document.createElement("label");
        // label과 checkbox input을 연결
        this.$label.htmlFor = "check";
        this.$label.addEventListener("mouseover", () => {
            this.$label.style.cursor = "pointer";
        });
        this.$toggle.appendChild(this.$label);

        // 다크모드일 경우 true, 라이트모드일 경우 false로 체크할 checkbox 타입의 input을 하나 생성
        this.$input = document.createElement("input");
        this.$input.type = "checkbox";
        this.$input.id = "check";
        this.$input.style.cssText = "display: none";
        this.$toggle.appendChild(this.$input);

        $target.appendChild(this.$toggle);

        // OS의 다크모드 활성화 여부에 따라 label과 input의 초깃값 및 body의 data-theme 속성을 설정
        let $theme = document.body.dataset.theme;
        if (!$theme) {
            // OS의 다크모드가 활성화되어 있는 경우
            if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
                $theme = "dark";
                this.$label.innerText = "☀ 라이트모드로 보기";
                this.$input.checked = true;   
            }
            // OS의 다크모드가 비활성화되어 있는 경우
            else {
                $theme = "light";
                this.$label.innerText = "🌙 다크모드로 보기";
                this.$input.checked = false;
            }
            document.body.dataset.theme = $theme;
            // 윗줄의 코드를 document.body.setAttribute("data-theme", $theme); 과 같이 작성할 수도 있음
        }

        this.render();
    }

    render() {
        // 토글 버튼을 클릭할 경우, checkbox input의 값에 따라 적절하게 body의 data-theme 속성 및 label 텍스트를 수정
        this.$toggle.addEventListener("click", (e) => {
            // 다크모드일 경우 (checkbox input의 값이 true일 경우)
            if (e.target.checked) {
                document.body.dataset.theme = "dark";
                this.$label.innerText = "☀ 라이트모드로 보기";
            }
            // 라이트모드일 경우 (checkbox input의 값이 false일 경우)
            else {
                document.body.dataset.theme = "light";
                this.$label.innerText = "🌙 다크모드로 보기";
            }
          
            // 아래와 같이 checkbox input 값 자체와 무관하게 이전 data-theme 속성을 기준으로 조건문을 작성할 수도 있음
          	/*
            if (document.body.dataset.theme === "dark") {
                document.body.dataset.theme = "light";
                this.$label.innerText = "🌙 다크모드로 보기";
            }
            else {
                document.body.dataset.theme = "dark";
                this.$label.innerText = "☀ 라이트모드로 보기";
            }
            */
        }); 
    }
}
profile
벨로그에는 주로 알고리즘 문제를 풀고 기록합니다 ✍

0개의 댓글