CSS/JavaScript로 어두운 테마를 지원하는 방법 (2019/9 업데이트)

(2019/9/27 추가)

iOS13 업데이트로 마침내 iOS Safari에서 테마를 얻을 수 있습니다.

macOS Mojave에서 화면을 눈에 띄는 검은 기조로 해주는 "다크 테마"가 도입되었습니다.
그 이후, 설정한 테마에 색조를 추종시키는 어플이 잇달아 나오네요.
이렇게 되면 웹 앱이나 웹 사이트도 테마에 맞추고 싶어집니다.
이번에 개발하고 있는 웹 앱(테마 전환 기능 자체는 도입이 끝난 상태)에서 macOS 테마에 추종하려고 하는 방법을 조사했기 때문에 정리합니다.

CSS Media Query에서 얻는 방법



Media Query에서 사용할 수 있는 미디어 특성은 prefers-color-scheme입니다.
이것은 사용자가 밝은 색인지 어두운 색인지를 가르쳐줍니다.
최근에 출시된 Safari 12.1부터 기본적으로 활성화되어 있으며 Firefox는 67부터 지원됩니다.
Chrome은 구현 작업 중인 것 같습니다.

실제로 CSS로 표시를 전환하고 싶은 경우는 다음과 같이 하면 됩니다.
@media (prefers-color-scheme: light) {
  body {
    background-color: white;
    color: black;
  }
}

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

실제로는 이런 느낌으로 CSS 변수에 정리하면 각 곳의 색을 일괄 변경할 수 있으므로 추천합니다.
@media (prefers-color-scheme: light) {
  html {
    --primary-color: black;
    --background-color: white;
  }
}

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

html {
  color: var(--primary-color);
  background-color: var(--background-color);
}

JavaScript에서 변경 사항을 감지하는 방법



처음 테마를 도입하는 경우는 위의 CSS에 의한 전환으로 좋다고 생각합니다.
단지 JavaScript에 의한 테마의 전환과 동시에 구현하고 싶은 경우는, 현재의 상태를 JavaScript측에서 취득할 필요가 있습니다.
Media Query에서 스타일을 전환하고 window.getComputedStyle를 사용하여 상태를 얻는 방법도 좋습니다.
하지만 OS 테마를 전환할 때 즉시 반응하기 위해서는 폴링해야 하며 비효율적입니다.

그래서 테마의 전환을 이벤트로 취득하기 위해 window.matchMedia 를 사용합니다.window.matchMedia에 일반 Media Query 문자열을 그대로 전달하면 MediaQueryList 개체를 얻을 수 있습니다.
const mql = window.matchMedia('(prefers-color-scheme: dark)')
MediaQueryList 객체의 matches 속성이 Media Query가 일치하는지 여부를 진위 값으로 가지고 있기 때문에,
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
  /* ダークテーマの時 */
} else {
  /* ライトテーマの時 */
}

우선 이것으로 상태의 취득이 가능합니다.

이벤트를 받으려면 다음을 수행합니다.
// ダークテーマの時にマッチするMediaQueryListオブジェクト
const isDark = window.matchMedia('(prefers-color-scheme: dark)')

// コールバック関数はMediaQueryListオブジェクトを受け取る
function toggleTheme (mql) {
  if (mql.matches) {
    /* ダークテーマの時 */
  } else {
    /* ライトテーマの時 */
  }
}

// イベントリスナーを追加
isDark.addListener(toggleTheme)

테마를 전환할 때마다 toggleTheme 함수가 호출되어 일치 상태에 따라 테마 전환 처리를 수행할 수 있습니다.

이번 내가 쓴 환경의 Vue.js+Vuex라고 이런 느낌입니다.
export default {
  name: 'app',
  methods: {
    toggleTheme(mql) {
      if (mql.matches) {
        this.$store.commit('updateTheme', 'dark')
      } else {
        this.$store.commit('updateTheme', 'light')
      }
    }
  },
  mounted() {
    this.$nextTick(() => {
      const isDark = window.matchMedia('(prefers-color-scheme: dark)')
      isDark.addListener(this.toggleTheme)
    })
  }
}

이제 어떤 느낌의 테마 기능을 구현할 수 있는지 둡니다.
왼쪽 하단의 달 아이콘이 테마의 토글 버튼입니다만, JavaScript측에도 상태가 반영되고 있는 것을 알 수 있다고 생각합니다.

좋은 웹페이지 즐겨찾기