본문 바로가기
JavaScript

클릭 이벤트가 상위 요소로 전파되는 것을 방지하는 방법

by chanfficial 2023. 6. 12.
<body>
    <div class="calendar-container">
      <h1>Calendar</h1>
      <input
        type="text"
        name="date-picker"
        id="date-picker-input"
        placeholder="Select date"
        readonly
      />

      <div class="calendar">
        <div class="calendar-nav">
          <div class="date-month">June</div>
          <div class="date-year">2023</div>
          <div class="arrow">
            <i class="bx bxs-left-arrow"></i> <i class="bx bxs-right-arrow"></i>
          </div>
        </div>
        <div class="calendar-grid">
          <ul class="date-week">
            <li class="sun">SUN</li>
            <li class="mon">MON</li>
            <li class="tue">TUE</li>
            <li class="wed">WED</li>
            <li class="thu">THU</li>
            <li class="fri">FRI</li>
            <li class="sat">SAT</li>
          </ul>

          <div class="date-day"></div>
        </div>
      </div>
    </div>
  </body>
.calendar {
  visibility: hidden;
  position: absolute;
  top: 31px;
  width: var(--calendar-width);
  height: calc(var(--calendar-width) * 1.2);
  border-radius: 10px;
  box-shadow: 0 0 20px gray;
}

.active {
  visibility: visible;
}

현재 '.calendar' 클래스를 가진 요소는 css 에서 visible: hidden 처리를 해두어 보이지 않는 상황이고,

 

Select date라고 적혀있는 input 태그를 클릭하면 '.calendar' 클래스를 가진 요소에 '.active' 클래스를 추가하여 '.calender' 요소가 나타나게 하는 함수를 작성하려고 한다.

 

 

아래는 처음 작성한 함수이다.

const datePicker = () => {
  const $datePickerInput = document.getElementById('date-picker-input');
  const $calendar = document.querySelector('.calendar');

  const addActiveClass = () => {
    $calendar.classList.add('active');
  };
  
  $datePickerInput.addEventListener('click', addActiveClass);
};

datePicker();

먼저 addActiveClass 함수를 생성하여 input 태그를 클릭했을 때 calendar 요소가 나타나는 것을 확인했다.

addActiveClass 함수가 잘 작동함

 그런 다음 $calendar 밖의 요소를 클릭했을 때, '.active' 클래스가 사라져 $calendar 요소가 보이지 않게 만들어 주는 removeActiveClass 함수를 구현했다.

 

나는 단순히 window 객체를 클릭했을 때 $calendar 요소에 '.active' 클래스가 있다면(= calendar 가 보인다면), 그 클래스를 제거해주면 된다고 생각했다.(= calendar 를 보이지 않게 해줌)

const datePicker = () => {
  const $datePickerInput = document.getElementById('date-picker-input');
  const $calendar = document.querySelector('.calendar');

  const addActiveClass = () => {
    $calendar.classList.add('active');
  };

  const removeActiveClass = (event) => {
    if ($calendar.classList.contains('active')) {
      $calendar.classList.remove('active');
    }
  };

  $datePickerInput.addEventListener('click', addActiveClass);
  window.addEventListener('click', removeActiveClass);
};

datePicker();

하지만 이렇게 함수를 구현하고 나니 잘 동작하던 addActiveClass 함수도 동작하지 않는 오류가 발생했다.

 

이는 단일 클릭 이벤트에 대한 동작이 겹치는 문제가 발생한 것인데, input 태그를 클릭했을 때 addActiveClass 함수가 호출되지만 클릭 이벤트는 상위 요소로 전파되기 때문에 window 객체의 클릭 이벤트 핸들러가 실행되고 removeActiveClass 함수가 호출되었기 때문이다.

 

 

즉, 순서대로 일어나는 동작을 정리해보면

1. input 요소 클릭 후 addActiveClass 함수가 실행되어 $calendar 요소에 '.active' 클래스가 추가되었다가
2. 상위 요소로 이벤트가 전파되어 window 객체의 클릭 이벤트를 처리하는 removeActiveClass 가 발생하는데
3. 이 함수는 '.active' 클래스를 제거하기 때문에 $calendar 요소가 사라지게 되고
결국 우리에게는 어떠한 함수도 동작하지 않은 것 처럼 보이는 것이다.

 

그렇기 때문에 클릭 이벤트가 상위 요소로 전파되는 것을 방지하기 위해서는 event.target 속성을 이용하여 input 요소를 클릭했다면 $calendar 의 '.active' 클래스가 유지되게 하는 조건을 추가해야 한다.

 

 

수정한 함수는 아래와 같다.

  const removeActiveClass = (event) => {
    if (event.target !== $datePickerInput && $calendar.classList.contains('active')) {
      $calendar.classList.remove('active');
    }
  };

이렇게 event 객체를 인자로 받아서 event 가 발생한 target 이 input 요소가 아니고 $calendar 요소에 '.active' 클래스가 포함되어 있다면,  $calendar 요소에서 '.active' 클래스가 제거하도록 코드를 수정했고 내가 원하는 대로 동작하는 것을 확인할 수 있었다.

 

input 요소 클릭 전
input 요소 클릭 후 calendar 가 나타남
input 요소 이외의 공간을 클릭하니 다시 calendar 가 사라짐