📋 目錄
當你需要實現彈出式 UI 時,現代瀏覽器提供了兩個強大的 API:Popover API 和 Dialog API。它們有何不同?什麼情況下該選哪一個?這篇文章幫你一次搞懂。
簡介:兩個 API 的定位
在過去,實現任何彈出式 UI(無論是下拉選單、工具提示、還是對話框)都需要依賴大量的 JavaScript 和 CSS,甚至需要自己處理無障礙功能。幸運的是,現代瀏覽器提供了兩個原生的解決方案:Popover API 和 Dialog API。
但很多開發者會疑惑:這兩個 API 到底有什麼差別?什麼情況下該用哪一個?
簡單來說:
- Popover API 適合大多數「浮層」類型的 UI
- Dialog API 適合真正的「對話框」場景
讓我們深入探討它們的差異。
Popover API:浮層的最佳選擇
什麼是 Popover?
Popover(彈出浮層)是指那些「附著在某個元素上」的 UI 元件,常見的有:
- 下拉選單(Dropdown)
- 工具提示(Tooltip)
- 氣泡提示(Toast/Notification)
- 選單按鈕(Menu Button)
這些 UI 的共同特點是:它們不是獨立存在的,而是「附著」在trigger 元素旁邊。
Popover API 的核心優勢
<!-- 使用 Popover API -->
<button popovertarget="my-menu">選單</button>
<div popover id="my-menu">
<ul>
<li>選項 1</li>
<li>選項 2</li>
<li>選項 3</li>
</ul>
</div>
-
極簡的宣告式 API 只需要在元素上加上
popover屬性,然後透過poptarget屬性關聯觸發按鈕。完全不需要寫 JavaScript 來控制顯示/隱藏。 -
內建的無障礙支援
- 自動處理 Focus 管理
- 支援鍵盤導航(Escape 關閉)
- 正確的 ARIA 屬性
-
自動定位 瀏覽器會自動將 Popover 定位在 trigger 元素附近,無需自己計算座標。
-
多個 Popover 的靈活性 可以同時開啟多個 Popover,彼此之間不會互相阻塞。
Popover API 的限制
- 不會建立最上層的疊加層(stacking context)
- 不會阻擋用戶與頁面其他部分的互動
- 沒有「強制聚焦」的功能
Dialog API:真正的對話框
什麼是 Dialog?
對話框(Dialog)是指那種「必須回應才能繼續」的 UI,常見場景:
- 確認對話框(Confirm Dialog)
- 表單對話框(Form Dialog)
- 警告視窗(Alert Dialog)
- 登入/註冊彈窗
這些 UI 的共同特點是:用戶必須與它互動,否則無法操作頁面其他部分。
Dialog API 的兩種模式
// 一般對話框(non-modal)
dialog.show(); // 顯示對話框,但不阻擋其他互動
// 強制對話框(modal)- 推薦使用
dialog.showModal(); // 顯示對話框,阻擋頁面其他互動
Dialog API 的核心優勢
-
真正的 Modal 體驗
showModal()會建立一個「最上層」的疊加層,阻擋用戶與頁面其他部分的互動。這是 Popover 做不到的。 -
完整的無障礙支援
- 自動 Focus 陷阱(Focus Trap):用戶無法將焦點移出對話框
- 背景「模糊」效果(由瀏覽器實現)
- Escape 鍵關閉
- 完整的 ARIA 支援
-
表單整合 對話框內的表單可以透過
dialog.returnValue取得回傳值,非常適合確認對話框。
<dialog id="confirm-dialog">
<form method="dialog">
<h2>確認刪除?</h2>
<button value="cancel">取消</button>
<button value="confirm">確認</button>
</form>
</dialog>
<script>
const dialog = document.getElementById('confirm-dialog');
dialog.showModal();
dialog.addEventListener('close', () => {
console.log('用戶選擇:', dialog.returnValue);
});
</script>
層級關係:Popover > Dialog > Modal Dialog
理解這三個層級的差異很重要:
| 特性 | Popover | Dialog | Modal Dialog |
|---|---|---|---|
| 阻擋頁面互動 | 否 | 否 | 是 |
| Focus 陷阱 | 否 | 否 | 是 |
| 背景遮罩 | 否 | 可選 | 自動 |
| 多個同時顯示 | 是 | 是 | 否 |
| 鍵盤關閉 | Escape | Escape | Escape |
選擇邏輯:
- 需要阻擋用戶操作頁面 → Modal Dialog(
showModal()) - 需要顯示對話框但不阻擋 → Dialog(
show()) - 只是顯示浮層 → Popover
實作複雜度比較
Popover API:極簡實作
<!-- 完整範例:下拉選單 -->
<style>
[popover] {
margin: 0;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
</style>
<button popovertarget="dropdown">開啟選單 ▼</button>
<div popover id="dropdown">
<a href="#">選項 A</a>
<a href="#">選項 B</a>
<a href="#">選項 C</a>
</div>
只需 HTML,無需 JavaScript!
Dialog API:需要更多程式碼
// 顯示 Modal 對話框
const dialog = document.querySelector('dialog');
// 開啟
dialog.showModal();
// 關閉(可從對話框內部觸發)
// <button onclick="this.closest('dialog').close()">關閉</button>
// 監聽關閉事件
dialog.addEventListener('close', () => {
// 處理關閉邏輯
});
總結:實作複雜度
| 任務 | Popover | Dialog |
|---|---|---|
| 基本顯示/隱藏 | ✅ 一行 HTML | ✅ 幾行 JS |
| 自訂位置 | ⚠️ 需額外 CSS | ✅ 可自訂 |
| 回傳值處理 | ❌ 不支援 | ✅ 支援 |
| 表單整合 | ❌ 不支援 | ✅ 支援 |
無障礙功能(Accessibility)對比
這是選擇 API 時最重要的考量之一。
Popover API 的無障礙支援
<!-- 瀏覽器自動處理 -->
<button popovertarget="menu" aria-expanded="false">選單</button>
<div popover id="menu">...</div>
瀏覽器會自動:
- 設定
aria-expanded狀態 - 處理 Escape 鍵關閉
- 管理焦點(但不會 trap)
Dialog API 的無障礙支援
<dialog aria-labelledby="dialog-title" aria-describedby="dialog-desc">
<h2 id="dialog-title">確認刪除</h2>
<p id="dialog-desc">此操作無法復原。</p>
...
</dialog>
瀏覽器會自動:
- 建立 Focus Trap(焦點陷阱)
- 模糊背景
- 正確的 ARIA 屬性
- Escape 鍵關閉
無障礙建議
- 如果是工具提示或下拉選單:使用 Popover
- 如果是需要用戶確認的對話框:使用 Dialog API +
showModal() - 永遠使用適當的
aria-label或aria-labelledby
實際應用場景
場景 1:下拉選單 ✅ Popover
<button popovertarget="user-menu" class="menu-trigger">
用戶選單 ▼
</button>
<div popover id="user-menu" class="dropdown">
<a href="/profile">個人資料</a>
<a href="/settings">設定</a>
<hr />
<a href="/logout">登出</a>
</div>
場景 2:工具提示 ✅ Popover
<button popovertarget="tooltip" class="icon-button">
<i class="info-icon">ℹ️</i>
</button>
<div popover id="tooltip" class="tooltip">
這是工具提示內容
</div>
場景 3:登入彈窗 ✅ Dialog + showModal()
<dialog id="login-dialog">
<h2>登入</h2>
<form method="dialog">
<label>帳號 <input type="text" name="username" /></label>
<label>密碼 <input type="password" name="password" /></label>
<button type="submit">登入</button>
<button type="button" onclick="this.closest('dialog').close()">取消</button>
</form>
</dialog>
場景 4:確認對話框 ✅ Dialog + showModal()
<dialog id="confirm-delete">
<form method="dialog">
<h2>確認刪除</h2>
<p>此操作無法復原,確定要繼續嗎?</p>
<button value="cancel">取消</button>
<button value="confirm">確認刪除</button>
</form>
</dialog>
總結:如何選擇?
| 問題 | 答案 |
|---|---|
| 需要阻擋用戶操作頁面? | → Dialog API (showModal()) |
| 需要表單回傳值? | → Dialog API |
| 只是附著在元素旁的浮層? | → Popover API |
| 需要多個同時顯示? | → Popover API |
| 想要最簡單的實作? | → Popover API |
記住這個簡單原則:
「需要用戶專注回應」的對話 → Dialog API 「輔助性顯示」的浮層 → Popover API
現在你應該能夠在專案中做出正確的選擇了。這兩個 API 大幅簡化了過去需要大量 JavaScript 才能實現的功能,不僅程式碼更少,無障礙支援也更好。趕快試試看吧!