반응형

이번 포스트에서는 JavaScript와 CSS를 활용해 (다크, 라이트, 시스템 테마) 전환 기능과 함께 인디케이터 애니메이션 효과를 구현하는 방법을 저장하려 합니다. 각 단계를 간략히라도 설명 했고 다음에 사용하기 좋게 만들었습니다.

 

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>테마 전환 예제</title>
  <style>
    /* 기본 스타일 및 애니메이션 효과 */
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 20px;
      transition: background-color 0.3s, color 0.3s;
    }
    /* 라이트 테마 (기본) */
    .light {
      background-color: #fff;
      color: #000;
    }
    /* 다크 테마 */
    .dark {
      background-color: #333;
      color: #fff;
    }
    /* 시스템 테마 예시 (여기서는 중간 톤) */
    .system {
      background-color: #f0f0f0;
      color: #000;
    }
    /* 테마 버튼 컨테이너 */
    .theme-container {
      position: relative;
      width: 300px;
      margin: 20px auto;
      display: flex;
      justify-content: space-between;
      border: 1px solid #ccc;
      border-radius: 30px;
      overflow: hidden;
    }
    /* 테마 버튼 */
    .theme-button {
      flex: 1;
      padding: 10px 0;
      text-align: center;
      cursor: pointer;
      z-index: 2;
      user-select: none;
    }
    /* 인디케이터: 버튼 아래로 이동하며 선택된 테마를 표시 */
    #indicator {
      position: absolute;
      bottom: 0;
      width: 33.33%; /* 3개의 버튼이므로 100/3% */
      height: 3px;
      background-color: #007BFF;
      transition: transform 0.3s;
      z-index: 1;
    }
  </style>
</head>
<body class="light">
  <h1>테마 전환 예제</h1>
  <!-- 테마 선택 컨테이너 -->
  <div class="theme-container">
    <div class="theme-button" data-theme="Light">Light</div>
    <div class="theme-button" data-theme="Dark">Dark</div>
    <div class="theme-button" data-theme="System">System</div>
    <div id="indicator"></div>
  </div>

  <p>본문 내용이 여기에 표시됩니다.</p>

  <script>
    // 인디케이터 요소와 테마 버튼들을 선택합니다.
    const indicator = document.getElementById("indicator");
    const buttons = document.querySelectorAll(".theme-button");

    // 현재 선택된 테마 (기본값: Light)
    let currentTheme = "Light";

    /**
     * 인디케이터의 위치를 설정하는 함수
     * @param {string} theme - "Light", "Dark", "System" 중 하나의 테마명
     * @param {boolean} isInitial - 초기 로드시 true, 버튼 클릭시 false
     */
    function setIndicatorPosition(theme, isInitial = false) {
      // 각 테마별 초기 오프셋을 퍼센트 단위로 지정합니다.
      const themeOffsets = {
        Light: "0%", // 'px'도 사용 가능
        Dark: "33.33%",
        System: "66.66%"
      };

      // 초기 오프셋 값 가져오기
      const offsetX = themeOffsets[theme];

      if (isInitial) {
        // 초기 로드 시 지정된 오프셋으로 이동
        indicator.style.transform = `translateX(${offsetX})`;
      } else {
        // 버튼 클릭 시, 버튼의 실제 위치로 인디케이터를 이동시키는 로직
        const themePositions = {
          Light: 0,
          Dark: 1,
          System: 2,
        };

        const selectedButton = buttons[themePositions[theme]];
        if (selectedButton) {
          const buttonRect = selectedButton.getBoundingClientRect();
          const parentRect = selectedButton.parentElement.getBoundingClientRect();
          // 부모 요소 기준으로 버튼의 왼쪽 오프셋 계산
          const calculatedOffsetX = buttonRect.left - parentRect.left;
          indicator.style.transform = `translateX(${calculatedOffsetX}px)`;
        }
      }
    }

    /**
     * 테마를 변경하는 함수
     * @param {string} theme - "Light", "Dark", "System" 중 하나의 테마명
     */
    function changeTheme(theme) {
      currentTheme = theme;
      // body 클래스 변경을 통해 테마 적용 (소문자 클래스 사용)
      document.body.className = theme.toLowerCase();
      // 인디케이터 위치 업데이트
      setIndicatorPosition(theme);
      // 필요 시, 로컬스토리지에 저장하거나 추가 작업 가능
      console.log("현재 테마:", theme);
    }

    // 각 버튼에 클릭 이벤트를 등록합니다.
    buttons.forEach(button => {
      button.addEventListener("click", function() {
        const selectedTheme = this.getAttribute("data-theme");
        changeTheme(selectedTheme);
      });
    });

    // 페이지 초기 로드 시 기본 테마에 따른 인디케이터 위치 설정
    window.addEventListener("DOMContentLoaded", function() {
      setIndicatorPosition(currentTheme, true);
    });
  </script>
</body>
</html>

 

JavaScript 동작 설명

  • setIndicatorPosition() 함수는 초기 로드와 버튼 클릭 시 인디케이터의 위치를 설정합니다. 초기 로드에서는 퍼센트 단위 오프셋을 사용하고, 클릭 시 실제 버튼의 위치를 계산합니다.
  • changeTheme() 함수는 선택된 테마에 따라 <body>의 클래스를 변경하여 스타일을 적용하고, 인디케이터를 이동시킵니다.
  • 각 버튼에는 클릭 이벤트 리스너가 등록되어 있어 클릭 시 해당 테마로 전환됩니다.
  • 페이지가 로드되면 기본 테마에 따른 인디케이터 위치가 설정됩니다.
반응형