컨텍스트 메뉴

Context menu

마우스 오른쪽 버튼(또는 길게 누르기)으로 커서 위치에 여는, 지금 클릭한 대상에 한정된 명령 목록.

다른 이름: Right-click menu우클릭 메뉴
···
html
<div class="cmw">
  <div class="cmtarget" id="cmt">여기를 마우스 오른쪽 버튼으로 눌러보세요<br><span class="hint">(우클릭)</span></div>
  <ul class="cmenu" id="cmenu" role="menu" aria-label="항목 메뉴">
    <li role="menuitem" tabindex="-1">이름 변경</li>
    <li role="menuitem" tabindex="-1">복제</li>
    <li role="menuitem" tabindex="-1" class="danger">삭제</li>
  </ul>
</div>
css
.cmw{position:absolute;inset:0}
.cmtarget{position:absolute;inset:14px;border:1px dashed var(--line);border-radius:10px;display:grid;place-items:center;text-align:center;color:var(--muted);font-size:11px;line-height:1.5}
.hint{color:var(--accent);font-weight:600}
.cmenu{position:absolute;list-style:none;margin:0;padding:5px;width:120px;background:var(--surface);border:1px solid var(--line);border-radius:10px;box-shadow:0 14px 30px rgba(0,0,0,.22);opacity:0;pointer-events:none;transform:scale(.94);transition:opacity .15s,transform .15s}
.cmenu[data-open]{opacity:1;pointer-events:auto;transform:scale(1)}
.cmenu li{padding:7px 9px;border-radius:6px;font-size:11px;color:var(--fg);cursor:default}
.cmenu li[data-hi]{background:var(--accent);color:#fff}
.cmenu li.danger{color:#e5484d}
.cmenu li.danger[data-hi]{background:#e5484d;color:#fff}
js
const t = document.getElementById('cmt'), menu = document.getElementById('cmenu');
const items = [...menu.querySelectorAll('li')];
function openAt(x, y) { menu.style.left = x + 'px'; menu.style.top = y + 'px'; menu.setAttribute('data-open', ''); }
function close() { menu.removeAttribute('data-open'); items.forEach((i) => i.removeAttribute('data-hi')); }
t.addEventListener('contextmenu', (e) => { e.preventDefault(); auto = false; openAt(e.offsetX, e.offsetY); });
document.addEventListener('click', (e) => { if (!menu.contains(e.target)) close(); });
let auto = true;
function loop() {
  if (!auto) return;
  openAt(60, 66);
  let i = 0;
  const hi = setInterval(() => {
    if (!auto) return clearInterval(hi);
    items.forEach((it, idx) => it.toggleAttribute('data-hi', idx === i));
    i++;
    if (i > items.length) { clearInterval(hi); setTimeout(() => { if (auto) { close(); setTimeout(loop, 900); } }, 300); }
  }, 380);
}
setTimeout(loop, 500);

preventDefault()로 브라우저 기본 컨텍스트 메뉴를 막고 그 자리에 커스텀 메뉴를 그립니다. 메뉴 자체의 마크업은 드롭다운 메뉴와 똑같이 role="menu"/role="menuitem"이고 방향키·Enter도 동일하게 동작합니다.

드롭다운 메뉴와 다른 점은 여는 방법과 위치입니다. 드롭다운 메뉴는 화면에 고정된 트리거 버튼을 클릭해 그 버튼 아래에 열리고 내용도 항상 같지만, 컨텍스트 메뉴는 마우스 오른쪽 버튼으로 커서가 있던 자리에 열리고 무엇을 우클릭했느냐에 따라 항목 구성이 달라질 수 있습니다. 터치 기기의 길게 누르기 변형은 mobile 카테고리의 "롱 프레스 컨텍스트 메뉴" 항목에서 따로 다룹니다.

우클릭은 마우스 전용 동작이라 키보드만 쓰는 사용자를 위해 Shift+F10이나 메뉴 키로도 열리게 하거나, 화면에 "⋯" 같은 보조 트리거 버튼을 함께 둬야 합니다.