Blue Chicken - The Legend Of Zelda
본문 바로가기

개인공부

[clickEvent] 버블링현상, event 객체 사용 이유

해당 코드에서는 backdrop을 눌러도 card를 눌러도 card가 닫히는 현상이 생김

더보기
<template>
    <button @click="toggleCard" class="toggleBtn">toggle</button>
    <!-- <div :class="{ 'backdrop': isShown }"> vue 동적 클래스 바인딩을 안쓰고 -->
    <div class="backdrop" @click="toggleCard">
        <div class="card" v-if="isShown">
            <label>REPORTS</label>
            <ul>
                <li>
                    <span class="icon"><i class="fa-solid fa-clock"></i></span>
                    Real-Time
                </li>
                <li>
                    <span class="icon"><i class="fas fa-user"></i></span>
                    Audience
                </li>
                <li>
                    <span class="icon"><i class="fa-solid fa-flag"></i></span>
                        Conversions
                    </li>
            </ul>
        </div>
    </div>
</template>
<script setup>
import {ref} from 'vue'

const isShown = ref(false);

const toggleCard = () => {
    const backdrop = document.querySelector('.backdrop');
    isShown.value = !isShown.value;
    backdrop.style.display = isShown.value? 'block' : 'none'; 
}

</script>
<style>
.toggleBtn{
    padding: 10px;
    border: 1px solid;
    border-radius: 5px;
    background: white;
}

.backdrop {
    display: none; /* vue 동적 클래스 바인딩 안 쓸 시 추가 */
    position: fixed; /* 화면 전체에 고정 */
    z-index: 10;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.7); /* 반투명 회색 */
}

.card {
    /* position: relative; */
    z-index: 20; /* backdrop 위에 위치 */
    width: 300px;
    padding: 15px 0 10px;
    margin: 150px auto;
    border: 1px solid gainsboro;
    border-radius: 5px;
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
    background: white; /* 카드 배경색 설정 */
}

.card > label {
    display: block; /* label을 블록 레벨 요소로 만들어 margin 적용 가능하게 함 */
    margin-bottom: 10px;
    padding: 0 20px;
    color: gray;
    font-weight: 400;
    text-align: left;
}

.card > ul {
    padding-left: 0; /* 왼쪽 패딩 제거 */
    list-style-type: none; /* 리스트 스타일 제거 */
    text-align: left;
}

.card > ul > li {
    padding: 0.25rem 20px; /* 각 항목 사이의 간격 조정 */
    font-weight: 500;
    cursor: pointer;
}

.card > ul > li:hover {
    background-color: #f0f0f0;
}

.card > ul > li > .icon {
    display: inline-block;
    width: 34px;
}

.card > ul > li > .icon > i{
    color:gray;
}

</style>

stopPropagation 는 상위 엘리먼트들로의 이벤트 전파를 중단시킨다.

https://pa-pico.tistory.com/20

 

[개념잡기] e.preventDefault() 와 stopPropagation() 의 차이

stopPropogation vs preventDefault e.preventDefault()와 e.stopPropagation()의 차이 두개의 코드 모두 이벤트 관련 동작에서 많이 사용되는 코드이다. 둘의 차이점은 무엇일까 알아보자. e.preventDefault() html 에서 a

pa-pico.tistory.com


문제의 원인은 `<div class="backdrop">` 요소에 클릭 이벤트를 할당하여 `toggleCard` 함수를 호출했기 때문입니다. 이 `<div>`는 `card`를 포함하고 있으므로 `card`를 클릭할 때도 `backdrop`의 이벤트가 실행되어 토글 기능이 작동하게 됩니다. 이는 이벤트 버블링 때문에 발생하는 현상으로, 클릭 이벤트가 자식 요소에서 부모 요소로 전파됩니다.

### 해결 방안: 이벤트 버블링 중지

`card` 요소 내부의 클릭 이벤트가 `backdrop`으로 전파되지 않도록 이벤트 버블링을 중지해야 합니다. 이를 위해 `card` 클릭 이벤트 핸들러에서 `event.stopPropagation()` 메서드를 사용할 수 있습니다. `stopPropagation()` 메서드는 이벤트가 더 이상 전파되지 않도록 막아 줍니다.

```vue

<template>
    <button @click="toggleCard" class="toggleBtn">toggle</button>
    <div class="backdrop" @click="toggleCard">
        <div class="card" v-if="isShown" @click.stop="preventClose">
            <label>REPORTS</label>
            <ul>
                <li>
                    <span class="icon"><i class="fa-solid fa-clock"></i></span>
                    Real-Time
                </li>
                <li>
                    <span class="icon"><i class="fas fa-user"></i></span>
                    Audience
                </li>
                <li>
                    <span class="icon"><i class="fa-solid fa-flag"></i></span>
                    Conversions
                </li>
            </ul>
        </div>
    </div>
</template>

<script setup>
import { ref } from 'vue';

const isShown = ref(false);

const toggleCard = () => {
    const backdrop = document.querySelector('.backdrop');
    isShown.value = !isShown.value;
    backdrop.style.display = isShown.value ? 'block' : 'none';
}

const preventClose = (event) => {
    event.stopPropagation();  // 이벤트 버블링을 막아 card 클릭 시 닫히지 않도록 함
}
</script>


```

### 변경된 부분:
- `card` 요소에 `@click.stop="preventClose"`를 추가하여 클릭 이벤트가 부모 요소로 전파되는 것을 막습니다. `.stop`은 Vue에서 이벤트 전파를 자동으로 중지하는 지시자입니다.
- `preventClose` 함수를 추가하여 클릭 이벤트를 처리합니다. 이 함수는 이벤트 객체를 받아 `stopPropagation()`을 호출합니다.

이렇게 하면 `card` 내부에서 일어나는 클릭 이벤트는 그 자리에서 처리되고, `backdrop`으로의 이벤트 전파가 중지되어 `card`를 클릭했을 때 창이 닫히는 문제를 방지할 수 있습니다.

 


이벤트 객체 사용 이유

 

  • toggleCard 함수: 이 함수는 버튼을 클릭했을 때 호출됩니다. 여기서는 버튼을 클릭할 때마다 카드가 보이거나 숨겨지게 만드는 기능만 수행합니다. 그래서 이벤트의 세부 정보(예: 어디에서 클릭했는지)는 필요하지 않습니다. 단순히 카드의 표시 상태만 바꾸면 되기 때문이죠.
  • preventClose 함수: 이 함수는 카드 내부의 클릭을 처리할 때 사용됩니다. 카드 내부의 어떤 부분을 클릭해도 백드롭(즉, 카드 뒤의 어두운 부분) 클릭으로 인식되어 카드가 닫히는 것을 막기 위해 필요합니다. 이 함수는 클릭 이벤트가 카드 외부로 전파되지 않도록 멈추어야 하므로, 이벤트의 전파를 중지하는 stopPropagation() 메서드를 호출하기 위해 이벤트 객체에 접근해야 합니다.

즉, toggleCard 함수는 "카드 보이기/숨기기"만 제어하고, preventClose는 "카드 내부 클릭 시 밖으로 이벤트가 전파되지 않도록" 제어합니다. 이런 차이 때문에 preventClose 함수에서는 이벤트 객체가 필요한 거죠!