매일 성장하기블로그

웹페이지 다크 모드 지원하기

prefers-color-scheme와 window.matchMedia 활용

웹페이지 다크 모드 지원은 prefers-color-scheme 미디어 쿼리를 사용해서 적용할 수 있다. 각 해상도에 따라 미디어 쿼리를 적용하는 방식과 크게 다르지 않다.

.title {
    color: #101010;
}

.desc {
    background-color: darkorange;
}

@media (prefers-color-scheme: dark) {
    .title {
        color: #EFEFEF;
    }
    .desc {
        background-color: peachpuff;
    }
}

이제 해당 페이지는 다크 모드로 지정했다면 해당 스타일로 적용된다. CSS 프로퍼티로 정의하면 자료와 구조를 분리할 수 있어 좀 더 깔끔해진다.

:root {
    --title-color: #101010;
    --desc-color: darkorange;
}

@media (prefers-color-scheme: dark) {
    :root {
        --title-color: #EFEFEF;
        --desc-color: peachpuff;
    }
}

.title {
    color: var(--title-color);
}

.desc {
    background-color: var(--desc-color);
}

사용자가 시스템에서 설정한 대로 보이긴 하지만 웹페이지에서 직접 변경할 수 있도록 옵션을 제공할 수도 있다. localStorage에 설정을 저장해서 다시 웹페이지에 접속할 때 마지막 설정을 불러올 수 있다.

:root {
    --title-color: #101010;
    --desc-color: darkorange;
}

@media (prefers-color-scheme: dark) {
    :root {
        --title-color: #EFEFEF;
        --desc-color: peachpuff;
    }
}

[data-theme="light"] {
    --title-color: #101010;
    --desc-color: darkorange;
}

[data-theme="dark"] {
    --title-color: #EFEFEF;
    --desc-color: peachpuff;
}

/* ... */

저장된 값이 있다면 페이지에 설정한다.

const theme = localStorage.getItem('theme');
if (theme) {
    document.documentElement.setAttribute('data-theme', theme);
}

시스템 설정을 확인하기 위해서 window.matchMedia() 함수를 사용할 수 있다. CSS의 미디어 쿼리가 현재 페이지에 해당하는지 확인하는 기능을 제공한다.

function toggleTheme() {
    // 저장된 값이 없다면 시스템 설정을 기준으로 함
    const currentTheme = localStorage.getItem('theme')
        || (
            window.matchMedia("(prefers-color-scheme: dark)").matches
            ? 'dark'
            : 'light'
        );
    const newTheme = currentTheme === 'dark' ? 'light' : 'dark';

    // 최상위 엘리먼트에 설정, 로컬 스토리지에 설정을 저장
    document.documentElement.setAttribute('data-theme', newTheme);
    localStorage.setItem('theme', newTheme);
}

만약 미디어 쿼리의 결과를 동적으로 반영해야 하는 경우에는 window.matchMedia()의 반환값인 MediaQueryList에 이벤트 리스너를 추가할 수 있다.

const mql = window.matchMedia("(prefers-color-scheme: dark)");

mql.addEventListener((e) => {
    if (e.matches) {
        // 해당 미디어 쿼리가 참인 경우
    } else {
        // 해당 미디어 쿼리가 거짓인 경우
    }
});