Front/Vue

[ Vue ] 다크모드 화이트모드 시스템 테마 설정하기

Yume Dev 2025. 5. 18. 18:27
반응형

이전에 react에서 했던 다크모드와 화이트모드 설정은 직접 class를 하나씩 주는 방식으로 했었는데

이 부분에서 많은 노가다가 필요했었기에 보통 페이지들에서 사용하는 방식인

data-theme을 주는 방식으로 진행


Style

style 폴더에서 variable.css파일을 생성하여 스타일 변수를 저장하는 파일로 관리

이후 main에 해당 css파일을 import해주면 된다.

 

data-theme이 white는 화이트 모드

data-theme이 dark는 다크모드

 

이때 변수명은 둘다 동일하게 가져가야한다.

:root {
    --app-root-padding: 10px;
}

:root[data-theme='white'] {
    --app-theme-side-background: #f3f3f3;
    --app-theme-background: #FFFFFF;
    --app-theme-text-color: #373737;
}

:root[data-theme='dark'] {
    --app-theme-side-background: #212121;
    --app-theme-background: #303030;
    --app-theme-text-color: #FFFFFF;
}

 

 

 

 

 

 

 

이후 vue파일에서 각 theme에 선언한 css 변수를 넣어주면 된다.

그럼 이제 dom에서 data-theme이 바뀜에 따라서 해당 변수의 색을 가져오므로
해당 변수만 적절한 위치에 선언해주면 된다.

<template>
  <div class="app-layout-wrapper">
    <div class="layout-sidebar">
      <side/>
    </div>
    <div class="layout-main">
      <app-main/>
    </div>
  </div>

</template>

<style scoped lang="scss">
.app-layout-wrapper {
  width: 100vw;
  height: 100vh;
  display: flex;

  .layout-sidebar {
    flex: 20;
    background: var(--app-theme-side-background);
    color: var(--app-theme-text-color);
  }
  .layout-main {
    flex: 80;
    background: var(--app-theme-background);
    color: var(--app-theme-text-color)
  }
}
</style>

Script

이제 data-theme을 변경해주는 함수를 만들어야한다.

나중에 오타를 방지하기 위해서 whte와 dark값을 변수로 미리 만들어서 사용

/* app theme relation */
export const APP_THEME_KEY = 'app-theme'

export const APP_THEME_WHITE = 'white'
export const APP_THEME_DARK = 'dark'

export type appThemeWhtie = typeof APP_THEME_WHITE
export type appThemeDark = typeof APP_THEME_DARK

 

appTheme함수를 만들어 초기 세팅용 init과 테마 적용용 get/set 함수를 생성

document.documentElement.setAttribute('data-theme', theme)

window.matchMedia('(prefers-color-scheme: dark)').matches;

 

window.matchMedia를 활용하면 시스템 테마를 가져 올 수 있다.

초기에 설정된 테마가 없다면 시스템 테마를 가져온다.

이때 시스템 테마는 true, false로 return되면 prefers-color-scheme가 dark인지 아닌지 비교해서 가져온다.

import type {appThemeDark, appThemeWhite} from "@/constant/local-storage.ts";
import {APP_THEME_KEY, APP_THEME_WHITE} from "@/constant/local-storage.ts";

export function appTheme() {
    const initTheme = () => {
        const theme = getTheme()
        if(!theme) {
            const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
            systemPrefersDark ? setTheme(APP_THEME_DARK) : setTheme(APP_THEME_WHITE)
        } else {
            setTheme(theme)
        }
    }

    const setTheme = (theme: appThemeWhite | appThemeDark) => {
        localStorage.setItem(APP_THEME_KEY,theme)
        document.documentElement.setAttribute('data-theme', theme);
    }

    const getTheme = (): appThemeWhite | appThemeDark | null => {
        const theme = localStorage.getItem(APP_THEME_KEY);
        return theme as appThemeWhite | appThemeDark | null;
    }

    return {
        initTheme,
        setTheme,
        getTheme
    }
}

 


Vue

그럼 이제 처음 실행시에는 app.vue가 먼저 실행되므로 해당 부분에서 getTheme을 호출해 테마를 적용시켜준다.

import { createApp } from 'vue'
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import './style/index.scss'
import App from './App.vue'
import router from "@/router";
import {appTheme} from "@/service";
const {initTheme} = appTheme()

initTheme()

const app = createApp(App)

app.use(ElementPlus)
app.use(router)
app.mount('#app')

 

이제 스위치를 통해서 값이 변경되면 setTheme을 호출해서 테마를 바꿔준다.

element-plus를 사용하므로 el-switch를 사용

import {appTheme} from "@/service";

const {setTheme,getTheme} = appTheme()
const themeMode = ref(getTheme())

const appThemeChange = (theme: appThemeWhtie | appThemeDark) => {
  setTheme(theme)
}
</script>

<template>
  <div class="app-side-wrapper">
    <div class="side-mode-change">
      <div class="flex-center">다크모드</div>
      <el-switch v-model="themeMode"
                 :active-value="APP_THEME_DARK"
                 :inactive-value="APP_THEME_WHITE"
                 @change="appThemeChange"
      />
    </div>

  </div>
</template>

 

watch를 활용해서 할까 하다가 그냥 @change로 사용

728x90
반응형