팝오버 API

Popover API

HTML `popover` 속성 하나로 top layer 렌더링, 라이트 디스미스, Esc 닫기를 공짜로 주는 네이티브 오버레이 API.

다른 이름: popover attributepopovertarget
···
html
<div class="wrap">
  <div class="badge" id="badge"><svg viewBox="0 0 10 10"><path id="bpath" d="M1.5 5.2l2.6 2.6L8.5 2.4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg><span id="btext">확인 중</span></div>
  <button id="btn" class="trigger" popovertarget="pop">알림 열기</button>
  <div id="pop" popover class="pop">
    <strong>새 메시지 3개</strong>
    <p>바깥을 누르거나 Esc를 누르면 닫혀요.</p>
  </div>
</div>
css
.wrap{position:relative;width:100%;height:100%;display:grid;place-items:center}
.trigger{padding:9px 18px;border-radius:9px;border:1px solid var(--line);background:var(--surface);color:var(--fg);font-size:13px;font-weight:600}
.pop{position:fixed;inset:auto auto 20% 50%;translate:-50% 0;margin:0;border:1px solid var(--line);border-radius:12px;padding:14px 16px;
  background:var(--surface);color:var(--fg);width:200px;box-shadow:0 16px 34px rgba(0,0,0,.22)}
.pop p{margin:4px 0 0;font-size:11px;color:var(--muted)}
.pop.fallback-open{display:block}
.badge{position:absolute;top:10px;right:10px;display:flex;align-items:center;gap:5px;padding:4px 9px;border-radius:999px;font-size:10px;font-weight:700;background:var(--bg);border:1px solid var(--line);color:var(--accent-3);z-index:2}
.badge.no{color:var(--accent-2)}
.badge svg{width:9px;height:9px}
js
const ok = 'popover' in HTMLElement.prototype;
const b=document.getElementById('badge'),p=document.getElementById('bpath'),t=document.getElementById('btext');
b.classList.toggle('no', !ok);
p.setAttribute('d', ok ? 'M1.5 5.2l2.6 2.6L8.5 2.4' : 'M2 2l6 6M8 2l-6 6');
t.textContent = ok ? 'popover 지원됨' : '미지원 · JS 폴백';
const pop = document.getElementById('pop'), btn = document.getElementById('btn');
if (!ok) {
  // 폴백: position:fixed + 수동 open/close + 바깥 클릭 감지
  pop.style.display = 'none';
  pop.removeAttribute('popover'); btn.removeAttribute('popovertarget');
  function open(){ pop.style.display = 'block'; }
  function close(){ pop.style.display = 'none'; }
  btn.addEventListener('click', (e) => { e.stopPropagation(); pop.style.display === 'block' ? close() : open(); });
  document.addEventListener('click', (e) => { if (!pop.contains(e.target)) close(); });
  window.__demoToggle = () => (pop.style.display === 'block' ? close() : open());
} else {
  window.__demoToggle = () => pop.togglePopover();
}
window.__demoToggle();
setInterval(() => window.__demoToggle(), 2200);

오버레이(드롭다운, 툴팁, 알림)를 만들 때 가장 성가신 부분은 z-index 전쟁과 "바깥을 클릭하면 닫히게" 로직이었습니다. z-index는 아무리 크게 줘도 부모 중 하나가 `overflow:hidden`이나 `transform`을 쓰면 잘려나갔고, 라이트 디스미스는 매번 document에 클릭 리스너를 달고 떼는 코드를 직접 짜야 했습니다.

`popover` 속성을 요소에 붙이면 그 요소는 top layer(뷰포트 최상단, 다른 모든 stacking context 위)로 렌더링됩니다. 트리거 버튼에는 `popovertarget="id"`만 주면 클릭으로 열고 닫히며, 바깥 클릭과 Esc는 브라우저가 알아서 처리합니다. JS로 열고 닫아야 하면 `el.showPopover()` / `hidePopover()` / `togglePopover()`를 씁니다.

기본 지원은 caniuse/MDN baseline을 확인하세요. 미지원 브라우저에서는 `position:fixed` + 수동 z-index + document 클릭 리스너 조합이 여전히 필요합니다 — 아래 데모의 폴백 경로가 그 최소형입니다.

언제 쓰나

드롭다운·알림·간단한 다이얼로그처럼 "다른 모든 것 위에 뜨고 바깥을 누르면 닫히는" UI를 만들 때.