Popover APIDialog API前端開發無障礙HTMLJavaScript

Popover API vs Dialog API:如何選擇?

深入比較 Popover API 和 Dialog API 的使用場景、實作複雜度與無障礙支援,幫助前端開發者在專案中做出正確的技術選擇。

· 5 分鐘閱讀

當你需要實現彈出式 UI 時,現代瀏覽器提供了兩個強大的 API:Popover API 和 Dialog API。它們有何不同?什麼情況下該選哪一個?這篇文章幫你一次搞懂。


簡介:兩個 API 的定位

在過去,實現任何彈出式 UI(無論是下拉選單、工具提示、還是對話框)都需要依賴大量的 JavaScript 和 CSS,甚至需要自己處理無障礙功能。幸運的是,現代瀏覽器提供了兩個原生的解決方案:Popover APIDialog 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>
  1. 極簡的宣告式 API 只需要在元素上加上 popover 屬性,然後透過 poptarget 屬性關聯觸發按鈕。完全不需要寫 JavaScript 來控制顯示/隱藏。

  2. 內建的無障礙支援

    • 自動處理 Focus 管理
    • 支援鍵盤導航(Escape 關閉)
    • 正確的 ARIA 屬性
  3. 自動定位 瀏覽器會自動將 Popover 定位在 trigger 元素附近,無需自己計算座標。

  4. 多個 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 的核心優勢

  1. 真正的 Modal 體驗 showModal() 會建立一個「最上層」的疊加層,阻擋用戶與頁面其他部分的互動。這是 Popover 做不到的。

  2. 完整的無障礙支援

    • 自動 Focus 陷阱(Focus Trap):用戶無法將焦點移出對話框
    • 背景「模糊」效果(由瀏覽器實現)
    • Escape 鍵關閉
    • 完整的 ARIA 支援
  3. 表單整合 對話框內的表單可以透過 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

理解這三個層級的差異很重要:

特性PopoverDialogModal Dialog
阻擋頁面互動
Focus 陷阱
背景遮罩可選自動
多個同時顯示
鍵盤關閉EscapeEscapeEscape

選擇邏輯

  1. 需要阻擋用戶操作頁面 → Modal DialogshowModal()
  2. 需要顯示對話框但不阻擋 → Dialogshow()
  3. 只是顯示浮層 → 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', () => {
  // 處理關閉邏輯
});

總結:實作複雜度

任務PopoverDialog
基本顯示/隱藏✅ 一行 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-labelaria-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 才能實現的功能,不僅程式碼更少,無障礙支援也更好。趕快試試看吧!