웹개발/Vue

Vue3에서 특정 요소 외의 바깥 영역 클릭시 이벤트 처리하기 (Feat. click-outside-vue3)

donsohn 2024. 1. 24. 00:15

프로젝트를 진행하다 보면 커스텀 컴포넌트를 직접 제작할 일이 매우 빈번하게 일어납니다.

그리고 이런 커스텀 컴포넌트 중 영역 외부 클릭 이벤트를 적용해야 하는 경우도 있죠. 예를 들면 드롭다운이 이러한 형태에 속합니다.

드롭다운은 누르면 펼쳐지고 다시 누르면 닫히게 되는데 사용자가 드롭다운 버튼을 통해서만 열린 드롭다운을 닫아야 한다면 다소 불편한 경험을 안겨줄 수 있을 것입니다.

이럴 때 사용하면 좋은 녀석 중 하나가 click-outside-vue3 라이브러리입니다.

비슷한 기능을 제공하는 패키지들이 여럿 있겠지만, 저는 click-outside-vue3를 통해서 엘리먼트 외부 클릭에 대한 처리를 편하게 했습니다.

설치

터미널에서 아래 명령어를 입력하여 패키지를 설치합니다.

npm

npm install --save click-outside-vue3

yarn

yarn add click-outside-vue3

사용하기

main.js

import { createApp } from 'vue';
import App from './App.vue';
import vClickOutside from 'click-outside-vue3';    // click-outside-vue3 패키지를 임포트합니다.

createApp(App).use(vClickOutside).mount('#app');    // .use(vClickOutside) 로 전역 사용하도록 추가해 줍니다.

먼저 click-outside-vue3 패키지를 import한 후, createApp(App).use(vClickOutside) 를 통해 해당 라이브러리를 사용하겠다고 선언해 줍니다.
그리고 mount('#app') 을 통해 마운트해 주면 됩니다.
이 라이브러리 뿐만 아니라 다른 라이브러리를 전역적으로 임포트해서 사용할 때는 마찬가지로 .use(이름) 을 연속적으로 체이닝해서 사용해도 됩니다.

App.vue (대상 컴포넌트)

<template>
    <div>
        <button type="button" @click="toggleDropdown" v-click-outside="vcoConfig">Dropdown</button>
        <ul class="dropdown" v-show="isDropdownOpen">
            <li class="dropdown-item">Dropdown item</li>
            <li class="dropdown-item">Dropdown item</li>
            <li class="dropdown-item">Dropdown item</li>
        </ul>
    </div>
</template>

<script>
export default {
    data() {
        return {
            isDropdownOpen: false,
            vcoConfig: {
                handler: this.hideDropdown,    // config 를 통해 이벤트 핸들러를 직접 지정
                middleware: this.middleware,    // 클릭된 요소가 특정 기능을 수행할 경우 이벤트 전파를 막기 위한 middleware 설정
                events: ['click'],    // 적용 이벤트 (dblclick 도 있음)
                isActive: true,    // v-click-outside 활성화 여부 (기본값 true)
                detectIFrame: true,    // iframe 감지 여부 (기본값 true)
                capture: false    // 대상 EventTarget의 addEventListener메서드에 영향을 준다.
                                 // 특정 이벤트 핸들러가 stopPropagation 메서드를 통해 이벤트 버블링을 방지하고자 할 때 유용할 수 있다.
            }
        }
    },
    methods: {
        toggleDropdown() {
            this.isDropdownOpen = !this.isDropdownOpen;
        },
        hideDropdown() {
            this.isDropdownOpen = false;
        },
        // middleware는 지정된 요소의 바깥을 클릭할 시에 발동된다.
        // 이 함수는 반드시 함수를 통해서만 실행이 가능하며 Boolean 타입으로 값을 리턴해야 반환된 true / false 값에 따라
        // middleware 핸들러가 작동하게 된다.
        // 아래 예시는 특정 기능을 수행할 가능성이 매우 높은 dropdown-item 이라는 클래스를 가진 요소를
        // middleware로 지정함으로써 해당 요소에 걸려 있는 이벤트가 실행 불가능하게 되는 상황을 방지하는 것이다.
        middleware(event) {
            return event.target.className === 'dropdown-item'
        }
    }
}
</script>

위 소스는 커스텀 드롭다운을 예시로 작성해본 것입니다.
드롭다운을 열어주는 buttonv-click-outside 속성을 통해 vcoConfig를 바인딩시킵니다.
vcoConfig는 아래 스크립트에 보시면 data() { ... } 내에서 정의되고 있습니다.
v-click-outside 패키지에서 지원하는 config 옵션들과 그에 대한 설명을 주석으로 표기하였습니다.

여기서 주의깊게 살펴볼 것은, vcoConfig 안의 middleware: this.middlewaremethods 에 정의되어 있는 middleware(event) { .. } 부분입니다.
별도로 middleware를 지정하지 않으면 v-click-outside 속성이 지정되어 있는 button 엘리먼트 바깥 영역이라면 그 어디를 눌러도 드롭다운 컴포넌트가 닫힐 것입니다.
단순히 이것만 생각한다면 문제가 없다고 보여질 수 있습니다. 하지만 대개는 드롭다운 안에 있는 자손 링크들이 특정 동작을 수행하는 경우가 많은데 이 자손 링크에 부여되어 있는 이벤트 핸들러가 모두 무시된다는 치명적인 현상이 발생하게 됩니다.

따라서 middleware 를 통해서 사용자가 클릭한 엘리먼트의 클래스명이 dropdown-item인 녀석을 여기에 대응시켜 줌으로써 드롭다운의 자손 링크인 <li class="dropdown-item">...</li> 이 의도한 동작을 성공적으로 수행시킬 수 있도록 해주는 것입니다.

Github : https://github.com/andymark-by/click-outside-vue3