最終更新:
Popover API完全ガイド: JavaScriptなしのポップオーバー実装
Popover APIは、JavaScriptなしでポップオーバー(ツールチップ、ドロップダウンメニュー、ダイアログなど)を実装できる新しいWeb標準APIです。2024年からモダンブラウザで広くサポートされ始めました。
Popover APIとは
Popover APIは、HTML属性だけでポップオーバーを実装できる機能です。これまでJavaScriptやライブラリが必要だった以下の機能を、ブラウザがネイティブサポートします:
- トップレイヤー表示(z-index管理不要)
- Escキーで閉じる
- フォーカス管理
- 外側クリックで閉じる
- アクセシビリティ(ARIA属性自動付与)
従来の実装との比較
<!-- 従来のJavaScript実装 -->
<button id="menuBtn">メニュー</button>
<div id="menu" class="hidden">
<ul>
<li>項目1</li>
<li>項目2</li>
</ul>
</div>
<script>
const btn = document.getElementById('menuBtn');
const menu = document.getElementById('menu');
btn.addEventListener('click', () => {
menu.classList.toggle('hidden');
});
// 外側クリックで閉じる
document.addEventListener('click', (e) => {
if (!menu.contains(e.target) && !btn.contains(e.target)) {
menu.classList.add('hidden');
}
});
// Escキーで閉じる
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
menu.classList.add('hidden');
}
});
</script>
<!-- Popover API使用(JavaScriptゼロ) -->
<button popovertarget="menu">メニュー</button>
<div id="menu" popover>
<ul>
<li>項目1</li>
<li>項目2</li>
</ul>
</div>
たった2つの属性で、Escキー、外側クリック、フォーカス管理が自動で実装されます。
基本的な使い方
シンプルなポップオーバー
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Popover API デモ</title>
<style>
[popover] {
padding: 1rem;
border: 2px solid #333;
border-radius: 8px;
background: white;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
</style>
</head>
<body>
<button popovertarget="info">詳細を表示</button>
<div id="info" popover>
<h3>追加情報</h3>
<p>ここに詳細な情報を表示します。</p>
</div>
</body>
</html>
3つのポップオーバータイプ
Popover APIには3つのモードがあります。
<!-- auto(デフォルト) - 他のポップオーバーを自動で閉じる -->
<button popovertarget="menu1">メニュー1</button>
<div id="menu1" popover="auto">
<p>メニュー1の内容</p>
</div>
<!-- manual - 明示的に閉じるまで開いたまま -->
<button popovertarget="notice" popovertargetaction="toggle">通知</button>
<div id="notice" popover="manual">
<p>この通知は手動で閉じる必要があります。</p>
<button popovertarget="notice" popovertargetaction="hide">閉じる</button>
</div>
<!-- hint - 軽量ヒント(aria-role="tooltip"相当) -->
<button popovertarget="tooltip1">ヘルプ</button>
<div id="tooltip1" popover="hint">
クリックして詳細を確認
</div>
popovertargetactionの種類
<button popovertarget="demo" popovertargetaction="show">開く</button>
<button popovertarget="demo" popovertargetaction="hide">閉じる</button>
<button popovertarget="demo" popovertargetaction="toggle">切り替え</button>
<div id="demo" popover>
<p>ポップオーバーコンテンツ</p>
</div>
実践的なユースケース
ドロップダウンメニュー
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
nav {
background: #2c3e50;
padding: 1rem;
}
nav button {
background: transparent;
border: none;
color: white;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 16px;
}
nav button:hover {
background: rgba(255,255,255,0.1);
border-radius: 4px;
}
[popover] {
margin: 0;
padding: 0;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
min-width: 200px;
}
[popover] ul {
list-style: none;
margin: 0;
padding: 0.5rem 0;
}
[popover] li a {
display: block;
padding: 0.75rem 1rem;
color: #333;
text-decoration: none;
}
[popover] li a:hover {
background: #f5f5f5;
}
[popover] hr {
margin: 0.5rem 0;
border: none;
border-top: 1px solid #eee;
}
</style>
</head>
<body>
<nav>
<button popovertarget="file-menu">ファイル</button>
<button popovertarget="edit-menu">編集</button>
<button popovertarget="view-menu">表示</button>
</nav>
<div id="file-menu" popover>
<ul>
<li><a href="#">新規作成</a></li>
<li><a href="#">開く</a></li>
<li><a href="#">保存</a></li>
<hr>
<li><a href="#">閉じる</a></li>
</ul>
</div>
<div id="edit-menu" popover>
<ul>
<li><a href="#">元に戻す</a></li>
<li><a href="#">やり直し</a></li>
<hr>
<li><a href="#">切り取り</a></li>
<li><a href="#">コピー</a></li>
<li><a href="#">貼り付け</a></li>
</ul>
</div>
<div id="view-menu" popover>
<ul>
<li><a href="#">拡大</a></li>
<li><a href="#">縮小</a></li>
<li><a href="#">リセット</a></li>
</ul>
</div>
</body>
</html>
ツールチップ
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
.tooltip-trigger {
text-decoration: underline dotted;
cursor: help;
color: #3498db;
}
[popover]:popover-open {
/* ツールチップスタイル */
padding: 0.5rem 0.75rem;
background: #333;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
max-width: 300px;
}
/* 矢印の追加 */
[popover]::before {
content: '';
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-bottom: 8px solid #333;
}
</style>
</head>
<body>
<p>
この機能は
<span class="tooltip-trigger" popovertarget="tip1">ベータ版</span>
です。利用にあたっては
<span class="tooltip-trigger" popovertarget="tip2">利用規約</span>
をご確認ください。
</p>
<div id="tip1" popover="hint">
現在テスト中の機能です。予告なく仕様が変更される可能性があります。
</div>
<div id="tip2" popover="hint">
サービス利用前に必ず規約をお読みください。
</div>
</body>
</html>
モーダルダイアログ
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
/* ポップオーバーをモーダル風にスタイル */
[popover] {
margin: auto;
padding: 2rem;
border: none;
border-radius: 12px;
background: white;
max-width: 500px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
/* バックドロップ(疑似要素) */
[popover]::backdrop {
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
}
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.dialog-header h2 {
margin: 0;
font-size: 1.5rem;
}
.close-btn {
background: transparent;
border: none;
font-size: 24px;
cursor: pointer;
color: #999;
}
.close-btn:hover {
color: #333;
}
.dialog-actions {
display: flex;
gap: 0.5rem;
justify-content: flex-end;
margin-top: 1.5rem;
}
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-primary {
background: #3498db;
color: white;
}
.btn-secondary {
background: #ecf0f1;
color: #333;
}
</style>
</head>
<body>
<button popovertarget="confirm-dialog">削除</button>
<div id="confirm-dialog" popover="manual">
<div class="dialog-header">
<h2>削除の確認</h2>
<button class="close-btn" popovertarget="confirm-dialog" popovertargetaction="hide">
×
</button>
</div>
<p>本当にこのアイテムを削除しますか?この操作は取り消せません。</p>
<div class="dialog-actions">
<button class="btn-secondary" popovertarget="confirm-dialog" popovertargetaction="hide">
キャンセル
</button>
<button class="btn-primary">削除</button>
</div>
</div>
</body>
</html>
アニメーション
CSSトランジション
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
[popover] {
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
/* 初期状態 */
opacity: 0;
transform: translateY(-20px);
/* トランジション */
transition: opacity 0.3s ease,
transform 0.3s ease,
display 0.3s allow-discrete,
overlay 0.3s allow-discrete;
}
/* 開いた状態 */
[popover]:popover-open {
opacity: 1;
transform: translateY(0);
}
/* 閉じるアニメーション(starting-style) */
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: translateY(-20px);
}
}
</style>
</head>
<body>
<button popovertarget="animated">アニメーション付きポップオーバー</button>
<div id="animated" popover>
<h3>滑らかなアニメーション</h3>
<p>開閉時にフェードとスライドのアニメーションが適用されます。</p>
</div>
</body>
</html>
CSS Animations
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
@keyframes slideIn {
from {
opacity: 0;
transform: scale(0.8) translateY(-20px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@keyframes slideOut {
from {
opacity: 1;
transform: scale(1) translateY(0);
}
to {
opacity: 0;
transform: scale(0.8) translateY(-20px);
}
}
[popover]:popover-open {
animation: slideIn 0.3s ease forwards;
}
[popover]:not(:popover-open) {
animation: slideOut 0.3s ease forwards;
}
</style>
</head>
<body>
<button popovertarget="bounce">バウンス効果</button>
<div id="bounce" popover>
<h3>バウンスアニメーション</h3>
<p>開く時に弾むような動きをします。</p>
</div>
</body>
</html>
JavaScript連携
Popover APIはJavaScriptなしでも動作しますが、より高度な制御も可能です。
イベントリスナー
<!DOCTYPE html>
<html lang="ja">
<head>
<style>
.status {
margin-top: 1rem;
padding: 0.5rem;
background: #f5f5f5;
border-radius: 4px;
}
</style>
</head>
<body>
<button popovertarget="tracked">ポップオーバー</button>
<div id="tracked" popover>
<p>イベント追跡が有効です</p>
</div>
<div class="status" id="status">
ステータス: 閉じています
</div>
<script>
const popover = document.getElementById('tracked');
const status = document.getElementById('status');
// ポップオーバーが開く前
popover.addEventListener('beforetoggle', (event) => {
console.log('beforetoggle:', event.newState);
if (event.newState === 'open') {
status.textContent = 'ステータス: 開いています...';
}
});
// ポップオーバーが開いた後
popover.addEventListener('toggle', (event) => {
console.log('toggle:', event.newState);
if (event.newState === 'open') {
status.textContent = 'ステータス: 開いています';
status.style.background = '#d4edda';
} else {
status.textContent = 'ステータス: 閉じています';
status.style.background = '#f5f5f5';
}
});
</script>
</body>
</html>
プログラム的な制御
<!DOCTYPE html>
<html lang="ja">
<body>
<div id="controlled" popover>
<p>JavaScriptで制御されるポップオーバー</p>
</div>
<button id="showBtn">表示</button>
<button id="hideBtn">非表示</button>
<button id="toggleBtn">切り替え</button>
<script>
const popover = document.getElementById('controlled');
document.getElementById('showBtn').addEventListener('click', () => {
popover.showPopover();
});
document.getElementById('hideBtn').addEventListener('click', () => {
popover.hidePopover();
});
document.getElementById('toggleBtn').addEventListener('click', () => {
popover.togglePopover();
});
// 条件付きで開く
setTimeout(() => {
if (Math.random() > 0.5) {
popover.showPopover();
}
}, 3000);
</script>
</body>
</html>
アクセシビリティ
Popover APIは自動的にARIA属性を付与しますが、さらに改善できます。
<!DOCTYPE html>
<html lang="ja">
<body>
<!-- 説明的なラベル -->
<button
popovertarget="settings"
aria-label="設定メニューを開く"
aria-haspopup="menu">
⚙️ 設定
</button>
<!-- role属性で意味を明確に -->
<div id="settings" popover role="menu">
<ul>
<li role="menuitem"><a href="#">プロフィール</a></li>
<li role="menuitem"><a href="#">通知設定</a></li>
<li role="menuitem"><a href="#">プライバシー</a></li>
</ul>
</div>
<!-- フォーカス可能な要素を含む場合 -->
<button popovertarget="form-popup">フォームを開く</button>
<div id="form-popup" popover>
<h2 id="form-title">お問い合わせ</h2>
<form aria-labelledby="form-title">
<label>
お名前
<input type="text" required>
</label>
<label>
メールアドレス
<input type="email" required>
</label>
<button type="submit">送信</button>
</form>
</div>
</body>
</html>
ブラウザ互換性とポリフィル
対応状況(2025年10月時点)
- Chrome 114+
- Edge 114+
- Safari 17+
- Firefox 125+
ポリフィル
<!DOCTYPE html>
<html lang="ja">
<head>
<script type="module">
// Popover APIのサポートチェック
if (!HTMLElement.prototype.hasOwnProperty('popover')) {
// ポリフィル読み込み
import('https://cdn.jsdelivr.net/npm/@oddbird/popover-polyfill@latest/dist/popover.min.js');
}
</script>
</head>
<body>
<button popovertarget="demo">デモ</button>
<div id="demo" popover>ポリフィルで動作します</div>
</body>
</html>
フォールバック実装
<style>
/* Popover非対応ブラウザ向けフォールバック */
[popover]:not(:popover-open) {
display: none;
}
@supports (top: anchor(top)) {
/* Anchor Positioning APIサポート時のスタイル */
}
</style>
まとめ
Popover APIは、以下の点で優れています:
- シンプル - HTMLだけで実装可能
- パフォーマンス - ブラウザネイティブ実装
- アクセシブル - ARIA属性自動付与
- メンテナンス性 - JavaScriptライブラリ不要
既存のツールチップやドロップダウンライブラリの置き換えとして、積極的に採用を検討する価値があります。