본문 바로가기

Development/Web (HTML, CSS)

블로그 다크 모드 토글 스위치 만들기

이번 포스팅에서는 블로그에 다크 모드를 적용하고, 다크/라이트 모드를 전환할 수 있는 토글 스위치를 만드는 방법을 알아보자.

특히, 사용자 기기의 기본 모드 설정을 우선으로 하고, 이후 사용자가 설정한 다크 모드 선택을 저장하여 브라우저에 기록하는 기능도 포함시킨다.

다크 모드 기본 설정 및 Font Awesome 아이콘 불러오기

먼저 다크 모드 구현을 위해 필요한 설정을 적용하고, 토글 스위치 아이콘에 사용할 Font Awesome 아이콘을 불러오자. 아이콘을 추가하기 위해서는 Font Awesome의 CDN 링크를 추가해야 한다. 이번에는 `fa-sun`과 `fa-moon` 아이콘을 사용할 것이다.

Font Awesome CDN 추가

<head>
  <!-- Font Awesome 아이콘 불러오기 -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

Font Awesome을 통해 태양과 달 모양의 아이콘을 사용하면 사용자가 선택한 테마에 따라 손쉽게 아이콘을 교체할 수 있다.

기본 다크 모드 설정

다크 모드의 스타일은 기본적으로 사용자의 시스템 설정을 우선으로 하며, 이후 사용자가 스위치로 선택한 모드가 있다면 해당 모드를 우선적으로 적용하게 된다. 다음과 같은 CSS에서 data-theme="dark" 속성을 기준으로 다크 모드를 정의한다. 이 속성을 이용해 다크 모드가 적용될 때 색상과 배경이 자동으로 변경될 수 있도록 한다.

다크 모드 스타일

/* 다크 모드 지원을 위한 추가 스타일 */
[data-theme="dark"] {
  /* 배경색 및 텍스트 색상 변경 */
  body {
    background-color: #121212;
    color: #e0e0e0;
  }
  .entry-content p {
    color: #e0e0e0 !important; /* 원하는 밝은 색상으로 변경 */
  }

  /* 헤더 링크 색상 변경 */
  #header h1 a,
  #header .mobile-menu span,
  #header .mobile-menu:before,
  #header .mobile-menu:after {
    color: #ffffff;
    /* background-color: #ffffff; */
  }

  /* 네비게이션 메뉴 색상 변경 */
  #gnb ul li a {
    color: #ffffff;
  }

  /* 컨텐츠 영역 배경 및 텍스트 색상 변경 */
  #content .inner,
  .entry-content {
    background-color: #121212;
    color: #e0e0e0;
  }


  a:hover {
    color: #636363;
  }

  /* 버튼 및 폼 요소 색상 변경 */
  .btn,
  input,
  textarea,
  select {
    background-color: #1f1f1f;
    color: #e0e0e0;
    border: 1px solid #333333;
  }

  /* 테두리 색상 변경 */
  hr,
  .pagination a,
  .pagination .prev,
  .pagination .next {
    border-color: #333333;
  }

  /* 기타 필요한 요소들의 색상 변경 */
  .post-item,
  .cover-thumbnail-list ul li a,
  .cover-masonry ul li a,
  .cover-list ul li a,
  .related-articles ul li a {
    /* background-color: #1f1f1f; */
    color: #e0e0e0;
  }

  /* 기존 box-shadow 설정을 다크 모드에 맞게 변경 */
  .post-item a,
  .cover-thumbnail-list ul li a,
  .cover-masonry ul li a,
  .cover-list ul li a,
  .related-articles ul li a,
  .tags .items a {
    color: #e0e0e0;
    background-color: #1f1f1f;
    box-shadow: -10px -10px 30px rgba(255, 255, 255, 0.05),
                10px 10px 30px rgba(0, 0, 0, 0.9);
  }

  .post-item {
    overflow: visible;
    /* 기타 속성들 */
  }
  /* ... 기타 요소들은 개발자 모드에서 태그 확인해가며 부족한 부분 채워넣는다.*/
}

이와 같이 data-theme="dark" 속성을 기준으로 스타일을 정의하면 다크 모드 전환이 간편해진다.

사용자 설정을 위한 다크 모드 토글 스위치 추가

사용자가 직접 다크 모드를 설정할 수 있도록 화면에 토글 스위치를 추가한다. 이 스위치는 화면 우측 하단에 고정된다.

토글 스위치 HTML 코드

위 코드에서 input과 label로 구성된 토글 스위치를 생성한다. label 안의 아이콘은 Font Awesome을 통해 다크 모드일 때 fa-moon(달) 아이콘, 라이트 모드일 때 fa-sun(태양) 아이콘으로 변경된다.

토글 스위치 스타일링

토글 스위치가 우측 하단에 고정되어 보이도록 스타일을 설정하고, 선택에 따라 손잡이와 배경색이 변경되도록 한다.

.mode-toggle {  
    position: fixed;  
    bottom: 20px;  
    right: 20px;  
    cursor: pointer;  
    z-index: 1000;  
}

#mode-switch {  
    display: none;  
}

.mode-toggle label {  
    position: relative;  
    display: block;  
    width: 50px;  
    height: 24px;  
    background-color: #ccc;  
    border-radius: 12px;  
    transition: background-color 0.3s;  
}

.mode-toggle label .switch-icon {  
    position: absolute;  
    top: 2px;  
    left: 2px;  
    width: 20px;  
    height: 20px;  
    background-color: white;  
    border-radius: 50%;  
    transition: transform 0.3s;  
    display: flex;  
    align-items: center;  
    justify-content: center;  
    font-size: 14px;  
}

#mode-switch:checked + label {  
    background-color: #4e4e4e;  
}

#mode-switch:checked + label .switch-icon {  
    transform: translateX(26px);  
}

이 스타일은 기본 배경 색상을 설정하고, 스위치가 켜질 때마다 배경 색상과 아이콘 위치가 변경되도록 설정한다.

다크 모드 시 토글 스위치
다크 모드 시 토글 스위치
라이트 모드 시 토글 스위치
라이트 모드 시 토글 스위치

다크 모드 토글 기능 구현하기

사용자가 다크 모드를 선택하면 `localStorage`에 해당 설정을 저장해 페이지 전환이나 새로고침에도 설정이 유지될 수 있도록 한다. 또한, 처음 페이지 로드 시에는 사용자의 시스템 설정을 우선 적용하여 시스템 다크 모드 설정을 따르도록 한다.

<script>
document.addEventListener('DOMContentLoaded', function () {
  const modeSwitch = document.getElementById('mode-switch');
  const switchIcon = document.querySelector('.switch-icon i');
  const userPreference = localStorage.getItem('theme');
  const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
  const initialMode = userPreference || systemPreference;

  document.documentElement.setAttribute('data-theme', initialMode);
  modeSwitch.checked = (initialMode === 'dark');

  // 초기 아이콘 설정
  if (initialMode === 'dark') {
    switchIcon.classList.remove('fa-sun');
    switchIcon.classList.add('fa-moon');
  } else {
    switchIcon.classList.remove('fa-moon');
    switchIcon.classList.add('fa-sun');
  }

  modeSwitch.addEventListener('change', function () {
    const theme = this.checked ? 'dark' : 'light';
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);

    // 아이콘 변경
    if (theme === 'dark') {
      switchIcon.classList.remove('fa-sun');
      switchIcon.classList.add('fa-moon');
    } else {
      switchIcon.classList.remove('fa-moon');
      switchIcon.classList.add('fa-sun');
    }
  });
});
</script>

동작 방식 설명

1. 초기 모드 설정: 페이지가 로드될 때 로컬 저장소에서 사용자 설정을 확인한다. 설정이 없다면 시스템 설정을 기준으로 초기 모드를 설정한다.
2. 아이콘 설정: 현재 모드에 따라 토글 스위치 아이콘을 `fa-sun`(라이트 모드) `또는 fa-moon`(다크 모드)로 표시한다.
3. 모드 전환: 스위치를 클릭하면 `data-theme` 속성이 변경되고, 변경된 설정을 로컬 저장소에 저장한다. 이후 아이콘도 즉시 업데이트된다.

또한 페이지 간 이동 시 CSS 적용 시간 차 문제로 잠깐동안 라이트 모드였다 다크 모드로 전환되어 깜박이는 현상을 방지하기 위해,

`head`에 다음 코드를 추가한다.

<script>
    (function() {
      const userPreference = localStorage.getItem('theme');
      const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
      const initialMode = userPreference || systemPreference;
      document.documentElement.setAttribute('data-theme', initialMode);
    })();
</script>

최종 코드

모든 코드가 적용된 최종 버전을 확인해보자.

<head>
    <!-- Font Awesome 아이콘 불러오기 -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
    <script>
        (function() {
          const userPreference = localStorage.getItem('theme');
          const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
          const initialMode = userPreference || systemPreference;
          document.documentElement.setAttribute('data-theme', initialMode);
        })();
    </script>
</head>

<body>
  <div class="mode-toggle">
    <input type="checkbox" id="mode-switch" aria-label="다크 모드 전환" />
    <label for="mode-switch">
      <span class="switch-icon">
        <i class="fas fa-sun"></i>
      </span>
    </label>
  </div>

  <!-- CSS -->
  <style>
    [data-theme="dark"] {
      /* 다크 모드 스타일 */
      /* ... (위에서 정의한 다크 모드 CSS) */
    }

    .mode-toggle {
      position: fixed;
      bottom: 20px;
      right: 20px;
      cursor: pointer;
      z-index: 1000;
    }

    #mode-switch {
      display: none;
    }

    .mode-toggle label {
      position: relative;
      display: block;
      width: 50px;
      height: 24px;
      background-color: #ccc;
      border-radius: 12px;
      transition: background-color 0.3s;
    }

    .mode-toggle label .switch-icon {
      position: absolute;
      top: 2px;
      left: 2px;
      width: 20px;
      height: 20px;
      background-color: white;
      border-radius: 50%;
      transition: transform 0.3s;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 14px;
    }

    #mode-switch:checked + label {
      background-color: #4e4e4e;
    }

    #mode-switch:checked + label .switch-icon {
      transform: translateX(26px);
    }
  </style>

  <!-- JavaScript -->
  <script>
    document.addEventListener('DOMContentLoaded', function () {
      const modeSwitch = document.getElementById('mode-switch');
      const switchIcon = document.querySelector('.switch-icon i');
      const userPreference = localStorage.getItem('theme');
      const systemPreference = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
      const initialMode = userPreference || systemPreference;

      document.documentElement.setAttribute('data-theme', initialMode);
      modeSwitch.checked = (initialMode === 'dark');

      if (initialMode === 'dark') {
        switchIcon.classList.remove('fa-sun');
        switchIcon.classList.add('fa-moon');
      } else {
        switchIcon.classList.remove('fa-moon');
        switchIcon.classList.add('fa-sun');
      }

      modeSwitch.addEventListener('change', function () {
        const theme = this.checked ? 'dark' : 'light';
        document.documentElement.setAttribute('data-theme', theme);
        localStorage.setItem('theme', theme);

        if (theme === 'dark') {
          switchIcon.classList.remove('fa-sun');
          switchIcon.classList.add('fa-moon');
        } else {
          switchIcon.classList.remove('fa-moon');
          switchIcon.classList.add('fa-sun');
        }
      });
    });
  </script>
</body>

이제 블로그에 다크 모드가 완벽하게 구현되었다. 기본적으로 사용자의 시스템 설정을 따르며, 선택한 모드는 로컬 저장소에 저장되어 지속적으로 유지된다.

다크 모드 토글 스위치 적용 결과
다크 모드 토글 스위치 적용 결과