개요
우클릭 메뉴(Context Menu)는 상황에 따라서 유용합니다. 모바일같은 경우 꾹 누르면 우클릭 메뉴가 뜨는 것처럼 할 수 있습니다.

popper.js 는 가볍고 강력한, 특정 요소에 붙어 있는 팝업 요소(popover)를 만들기 좋은 라이브러리입니다. 이것을 이용해보도록 하겠습니다.
- 데모링크 : https://ezkorry.gitlab.io/simple-context-menu
- 소스코드: https://gitlab.com/EzKorry/simple-context-menu
설계
우클릭 메뉴를 설계할 때 고려해야 할 사항은 다음과 같습니다. UI 자체야 더 고급스럽고 사용성 좋게 만들려면 한도 끝도 없지만, 아래 기능은 무조건 충족시키도록 해보자구요.
- 우클릭 메뉴가 활성화되는 지점(캔버스)이 정해져 있어야 합니다. 아무데서나 우클릭해서 전부 우클릭 메뉴가 뜬다면 UI 로써 기능이 좀 거시기하겠죠..?
- 다음과 같을 때 우클릭 메뉴가 사라져야 합니다.
- 우클릭 메뉴가 활성화된 상태에서 메뉴의 바깥을 눌렀을 때
- 우클릭 메뉴 안에 있는 링크(버튼)을 눌렀을 때
- 우클릭 메뉴의 항목을 클릭했을 때의 동작을 커스터마이징 하기 쉬워야 합니다.
- 단순히 우클릭 메뉴 (Container) 안에 또 다른 컴포넌트 (Item)를 넣기만 해도 자연스럽게 의존 관계가 생길 수 있도록 해봅시다. (예를 들어 Container의 props 를 통해 Item 용
class
를 일괄 적용시킬 수 있도록 하기)
기능 적인 요소 외로 그냥 사용하기 편하도록 하려면 어떻게 해야 할까요? 자잘한 고려사항을 아래와 같이 정해보았습니다.
- 메뉴를 가져다쓰는 입장에서 모든 스타일을 지정할 수 있도록, 처음에는 아무런 스타일을 지정하지 않습니다. 클래스 명이나 css 속성을 통해 스타일을 마음대로 주무를 수 있도록 했습니다.
목표 코드
실제로 해당 컴포넌트를 어떻게 사용할 건지 먼저 정하고, 컴포넌트를 만들어 가는 것도 괜찮은 방법입니다. 아래는 App.vue
입니다. 템플릿 부분, 스크립트 부분, CSS 부분을 나눠서 보겠습니다.
<template>
<div>
<div
v-ec-contextmenu:my-context
:style="{ width: '200px', height: '200px', backgroundColor: '#eee' }"
>
이 칸에서 우클릭을 해보세요.
</div>
<ec-container
class="context-menu-container"
style="text-align: left"
id="my-context"
:itemStyle="{ display: 'block' }"
itemClass="font-bold"
v-slot="api"
>
<ec-item @click="log('첫 번째 메뉴가 눌렸습니다.')">첫 번째 메뉴</ec-item>
<ec-item @click="log('두 번째 메뉴가 눌렸습니다.')">두 번째 메뉴</ec-item>
<ec-item @click="log('세 번째 메뉴가 눌렸습니다.')">세 번째 메뉴</ec-item>
<div>안녕하십니까!!</div>
<button @click="api.close">이 버튼을 클릭하면 닫습니다.</button>
<pre>state: {{ api.state }}</pre>
</ec-container>
</div>
</template>
import { defineComponent } from "vue";
import { EcContainer, EcItem, EcDirective } from "./components/MyContextMenu";
export default defineComponent({
name: "App",
directives: {
EcContextmenu: EcDirective,
},
components: {
EcContainer,
EcItem,
},
setup() {
return {
log: console.log,
};
},
});
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.context-menu-container {
background-color: #fff;
border: 1px solid #ddd;
padding: 15px;
}
.font-bold {
font-weight: bold;
}
위 코드에서 우리가 설계했던 부분이 어떤 부분인지 한번 짚겠습니다.
- 우클릭이 활성화되는 지점은
v-ec-contextmenu:my-context
로 지정했습니다.my-context
는 우클릭 메뉴의id
값입니다.v-ec-contextmenu
는 vue에서 directive 라는 기능으로, App 컴포넌트 정의에서directives: { EcContextmenu: EcDirective, }
로 지정해주고 있습니다. 정리하면 다음과 같습니다.- 우클릭이 활성화되는 지점은
v-ec-contextmenu
directive 가 있는 HTML 엘리먼트이다. - 우클릭을 눌렀을 때
my-context
를 찾아 우클릭 메뉴를 동작시키도록 한다.
- 우클릭이 활성화되는 지점은
- 메뉴 항목을 클릭했을 때의 동작은
@click="..."
를 통해 지정해줍니다. 동작을 커스터마이징하기 쉽습니다. ec-container
컴포넌트에서itemStyle
과itemClass
props 를 통해 하위의 모든ec-item
컴포넌트가 영향을 받고 있습니다. 이로부터 위치만 내부에 있는 컴포넌트가 의존성을 가질 수 있다는 걸 확인할 수 있습니다.
구현
저장 공간 만들기
우클릭 했을 때
각각의 컴포넌트마다 만들어진 이벤트를 수신하기 위해, 한 단계를 거쳐나가야 합니다.
한계
popper.js 는 객체의 나타나고 안나타나고를 css의 inset(left, right, top, bottom 등)
과 transform
을 이용합니다. 그래서 만약 부드러운 애니메이션을 만들고 싶지만 그러한 속성을 사용하기 어려울 수 있습니다. 그래서 상위 엘리먼트든 하위 앨리먼트든 만들어서 transform
과 transition
등을 적절히 적용해야 할 것으로 보입니다.