彈出式視窗:他們和 #39 的爆發,正要復甦!

Open UI 計畫的目標是讓開發人員更輕鬆地打造優質使用者體驗。為此,我們正設法解決開發人員面臨的較棘手模式。我們將提供更完善的平台內建 API 和元件,協助您達成這個目標。

其中一個問題領域是彈出式視窗,在 Open UI 中稱為「Popover」。

長期以來,彈出式視窗的評價相當兩極。這部分是因為建構和部署方式所致。雖然要建構完善的模式並不容易,但如果使用得當,就能引導使用者前往特定內容,或讓他們瞭解網站上的內容,進而創造許多價值。

建構浮動視窗時,通常有兩大疑慮:

  • 如何確保廣告顯示在適當位置,且位於其他內容上方。
  • 如何讓使用者存取 (可透過鍵盤操作、可聚焦等)。

內建的 Popover API 有多種目標,但最終目標都是要讓開發人員輕鬆建構這個模式。其中值得注意的目標包括:

  • 輕鬆將元素及其後代顯示在文件其餘部分上方。
  • 方便存取。
  • 不需要 JavaScript 即可執行最常見的行為 (輕觸即可關閉、單例、堆疊等)。

如要查看彈出式視窗的完整規格,請前往 OpenUI 網站

瀏覽器相容性

您現在可以在哪些地方使用內建的 Popover API?撰文時,Chrome Canary 支援這項功能,但必須啟用「實驗性網路平台功能」旗標。

如要啟用該旗標,請開啟 Chrome Canary 並前往 chrome://flags。然後啟用「實驗性網頁平台功能」旗標。

開發人員也可以透過原始碼試用功能,在正式環境中測試這項功能。

最後,API 的 polyfill 正在開發中。請務必前往 github.com/oddbird/popup-polyfill 查看存放區。

你可以透過下列方式查看彈出式支援:

const supported = HTMLElement.prototype.hasOwnProperty("popover");

現有解決方案

目前可以採取哪些行動,讓你的內容脫穎而出?如果瀏覽器支援,可以使用 HTML 對話方塊元素。您必須以「強制回應」形式使用。且必須使用 JavaScript。

Dialog.showModal();

請留意無障礙設計注意事項。如果服務對象是 15.4 以下版本的 Safari 使用者,建議使用 a11y-dialog

您也可以使用眾多以快顯視窗、快訊或工具提示為基礎的程式庫。其中許多工具的運作方式都大同小異。

  • 將一些容器附加至主體,以顯示彈出式視窗。
  • 設定樣式,讓它位於所有其他項目上方。
  • 建立元素並附加至容器,即可顯示彈出式視窗。
  • 從 DOM 移除快顯視窗元素,即可隱藏快顯視窗。

這需要額外的依附元件,開發人員也必須做出更多決策。此外,您也需要研究,找出能滿足所有需求的產品。Popover API 適用於許多情境,包括工具提示。目標是涵蓋所有常見情境,讓開發人員不必再做其他決定,專心建構體驗。

第一個彈出式視窗

這樣就夠了。

<div id="my-first-popover" popover>Popover Content!</div>
<button popovertoggletarget="my-first-popover">Toggle Popover</button>

但這裡發生了什麼事?

  • 您不必將彈出式視窗元素放入容器或其他任何項目,因為系統預設會隱藏該元素。
  • 您不必撰寫任何 JavaScript,就能顯示這類廣告。這項作業是由 popovertoggletarget 屬性處理。
  • 顯示時,系統會將其升級至頂層。也就是說,系統會將其升級,在可視區域中顯示於 document 上方。您不必管理 z-index,也不必擔心快顯視窗在 DOM 中的位置。這可能是因為該元素深層巢狀內嵌在 DOM 中,且具有剪輯祖先。您也可以透過開發人員工具,查看目前位於頂層的元素。如要進一步瞭解頂層,請參閱這篇文章

GIF:示範開發人員工具頂層支援功能

  • 您可直接使用「輕觸即關閉」功能。也就是說,您可以透過關閉信號關閉快顯視窗,例如點選快顯視窗以外的區域、使用鍵盤導覽至其他元素,或是按下 Esc 鍵。再次開啟應用程式,然後試試看!

使用彈出式視窗還能獲得哪些好處?我們來進一步舉例說明。請參考這個示範,瞭解網頁上的部分內容。

該懸浮動作按鈕具有固定位置,且 z-index 較高。

.fab {
  position: fixed;
  z-index: 99999;
}

彈出式視窗內容會巢狀內嵌在 DOM 中,但開啟彈出式視窗時,系統會將其升級至固定位置元素上方。您不需要設定任何樣式。

您可能也會發現,彈出式視窗現在有 ::backdrop 虛擬元素。頂層的所有元素都會取得可設定樣式的 ::backdrop 虛擬元素。這個範例會使用降低 Alpha 值的背景顏色和背景篩選器,設定 ::backdrop 的樣式,模糊處理底層內容。

設定彈出式視窗樣式

接著來看看如何設定彈出式視窗的樣式。根據預設,彈出式視窗會固定在某個位置,並套用一些邊框間距。此外,這項功能還提供 display: none。您可以覆寫這項設定,顯示彈出式視窗。但這樣不會將其升級至頂層。

[popover] { display: block; }

無論您如何宣傳快顯視窗,只要將快顯視窗宣傳至頂層,您可能就需要配置或放置快顯視窗。您無法指定頂層,並執行類似下列的操作:

:open {
  display: grid;
  place-items: center;
}

根據預設,系統會使用 margin: auto 將彈出式視窗配置在檢視區塊中央。但在某些情況下,您可能需要明確指定位置。例如:

[popover] {
  top: 50%;
  left: 50%;
  translate: -50%;
}

如要使用 CSS 格線或 Flexbox 排列彈出式視窗內的內容,建議將內容包裝在元素中。否則,您需要宣告個別規則,在快顯視窗位於頂層時變更 display。如果設為預設值,系統就會預設顯示,並覆寫 display: none

[popover]:open {
 display: flex;
}

如果您試用過該範例,會發現彈出式視窗現在會轉場進出。您可以使用 :open 虛擬選取器,讓浮動視窗淡入和淡出。:open 虛擬選取器會比對顯示的 (因此位於頂層) 浮動視窗。

這個範例使用自訂屬性來驅動轉場效果。您也可以將轉場效果套用至彈出式視窗的 ::backdrop

[popover] {
  --hide: 1;
  transition: transform 0.2s;
  transform: translateY(calc(var(--hide) * -100vh))
            scale(calc(1 - var(--hide)));
}

[popover]::backdrop {
  transition: opacity 0.2s;
  opacity: calc(1 - var(--hide, 1));
}


[popover]:open::backdrop  {
  --hide: 0;
}

建議您將轉場效果和動畫歸類在動作的媒體查詢下。這也有助於維持你的時間。這是因為您無法透過自訂屬性在 popover::backdrop 之間共用值。

@media(prefers-reduced-motion: no-preference) {
  [popover] { transition: transform 0.2s; }
  [popover]::backdrop { transition: opacity 0.2s; }
}

到目前為止,您已瞭解如何使用 popovertoggletarget 顯示彈出式視窗。如要關閉,我們使用「輕觸關閉」。但你也可以使用 popovershowtargetpopoverhidetarget 屬性。讓我們在彈出式視窗中新增按鈕,隱藏彈出式視窗,並將切換按鈕變更為使用 popovershowtarget

<div id="code-popover" popover>
  <button popoverhidetarget="code-popover">Hide Code</button>
</div>
<button popovershowtarget="code-popover">Reveal Code</button>

如先前所述,Popover API 的涵蓋範圍不只我們過去對彈出式視窗的概念。您可以為所有類型的情境建構,例如通知、選單、工具提示等。

在某些情況下,需要採用不同的互動模式。例如滑鼠游標懸停。我們曾實驗使用 popoverhovertarget 屬性,但目前尚未實作。

<div popoverhovertarget="hover-popover">Hover for Code</div>

也就是說,只要將游標懸停在元素上,就會顯示目標。這項行為可透過 CSS 屬性設定。這些 CSS 屬性會定義時間視窗,讓彈出式視窗對元素懸停和離開做出反應。實驗的預設行為是在明確 0.5s :hover 後顯示彈出式視窗。然後需要輕觸即可關閉,或是開啟另一個彈出式視窗來關閉 (詳情請見下文)。這是因為彈出式視窗的隱藏時間長度設為 Infinity

在此期間,您可以使用 JavaScript 來填補該功能。

let hoverTimer;
const HOVER_TRIGGERS = document.querySelectorAll("[popoverhovertarget]");
const tearDown = () => {
  if (hoverTimer) clearTimeout(hoverTimer);
};
HOVER_TRIGGERS.forEach((trigger) => {
  const popover = document.querySelector(
    `#${trigger.getAttribute("popoverhovertarget")}`
  );
  trigger.addEventListener("pointerenter", () => {
    hoverTimer = setTimeout(() => {
      if (!popover.matches(":open")) popover.showPopover();
    }, 500);
    trigger.addEventListener("pointerleave", tearDown);
  });
});

明確設定懸停視窗的好處是,可確保使用者是有意採取動作 (例如使用者將指標移到目標上)。除非使用者有意開啟,否則我們不希望顯示彈出式視窗。

請試用這個示範,將視窗設為 0.5s,然後將滑鼠游標懸停在目標上。


在探討一些常見用途和範例之前,我們先瞭解幾件事。


快顯視窗類型

我們已介紹非 JavaScript 互動行為。但整體而言,快顯視窗的行為如何?如果不想使用「輕觸即可關閉」功能,或者想將單例模式套用至彈出式視窗?

Popover API 可讓您指定三種不同行為的彈出式視窗。

[popover=auto]/[popover]

  • 支援巢狀結構。這不只代表巢狀結構的 DOM,祖先快顯視窗的定義如下:
    • 依 DOM 位置 (子項) 相關。
    • 在子元素上觸發屬性 (例如 popovertoggletargetpopovershowtarget 等) 時,會觸發相關事件。
    • (由開發中的 CSS Anchoring API 相關聯)。anchor
  • 輕觸即可關閉。
  • 開啟時,系統會關閉其他非祖先浮動式視窗的浮動式視窗。請試用下方的示範,瞭解如何使用祖先快顯視窗巢狀結構。瞭解將部分 popoverhidetarget/popovershowtarget 執行個體變更為 popovertoggletarget 後,會發生什麼變化。
  • 輕觸即可關閉所有通知,但如果輕觸堆疊中的通知,只會關閉堆疊中位於該通知上方的通知。

[popover=manual]

  • 不會關閉其他彈出式視窗。
  • 不會關閉螢幕。
  • 需要透過觸發元素或 JavaScript 明確關閉。

JavaScript API

如要進一步控管彈出式視窗,可以使用 JavaScript。您會取得 showPopoverhidePopover 方法。您也可以監聽 popovershowpopoverhide 事件:

顯示彈出式視窗 js popoverElement.showPopover() 隱藏彈出式視窗:

popoverElement.hidePopover()

監聽彈出式視窗是否顯示:

popoverElement.addEventListener('popovershow', doSomethingWhenPopoverShows)

監聽顯示的彈出式視窗,並取消顯示:

popoverElement.addEventListener('popovershow',event => {
  event.preventDefault();
  console.warn(We blocked a popover from being shown);
})

監聽彈出式視窗是否隱藏:

popoverElement.addEventListener('popoverhide', doSomethingWhenPopoverHides)

您無法取消隱藏的快顯視窗:

popoverElement.addEventListener('popoverhide',event => {
  event.preventDefault();
  console.warn("You aren't allowed to cancel the hiding of a popover");
})

檢查快顯視窗是否位於頂層:

popoverElement.matches(':open')

這可為一些較不常見的情況提供額外功能。舉例來說,在閒置一段時間後顯示彈出式視窗。

這個示範包含會發出聲音的快顯視窗,因此我們需要使用 JavaScript 播放音訊。點按時,我們會隱藏快顯視窗、播放音訊,然後再次顯示。

無障礙設定

Popover API 的設計理念以無障礙功能為優先考量。無障礙對應會視需要將快顯視窗與觸發元素建立關聯。也就是說,如果您使用 popovertoggletarget 等觸發屬性,就不必宣告 aria-* 屬性 (例如 aria-haspopup)。

如要管理焦點,可以使用自動對焦屬性,將焦點移至快顯視窗內的元素。這與對話方塊相同,但傳回焦點時有所不同,這是因為輕觸即可關閉。在大多數情況下,關閉快顯視窗會將焦點移回先前焦點元素。但如果點選的元素可以取得焦點,焦點就會在輕觸關閉時移至該元素。請參閱說明文件中的焦點管理部分

您必須開啟這個示範的「全螢幕版本」,才能查看運作情形。

在本示範中,系統會為聚焦元素加上綠色外框。嘗試使用鍵盤在介面中切換分頁。請注意,當快顯視窗關閉時,焦點會返回何處。您可能也會發現,如果切換分頁,彈出式視窗就會關閉。這是正常的。雖然快顯視窗有焦點管理功能,但不會鎖定焦點。當焦點移出彈出式視窗時,鍵盤導覽會識別關閉信號。

錨定 (開發中)

就快顯視窗而言,要配合的棘手模式是將元素錨定至觸發程序。舉例來說,如果工具提示設定為顯示在觸發元素上方,但文件遭到捲動,該工具提示可能會遭到檢視區塊截斷。目前有 JavaScript 產品可處理這類問題,例如「Floating UI」。他們會重新放置工具提示,避免這種情況發生,並依據所需位置順序放置。

但我們希望您能使用樣式定義這項屬性。為解決這個問題,我們正在開發與 Popover API 搭配使用的 API。「CSS 錨點定位」API 可讓您將元素繫結至其他元素,並以重新定位元素的方式,確保元素不會遭到可視區域截斷。

這個範例目前使用 Anchoring API。船隻的位置會根據錨點在可視區域中的位置而有所不同。

以下是讓這個示範正常運作的 CSS 片段。無須使用 JavaScript。

.anchor {
  --anchor-name: --anchor;
}
.anchored {
  position: absolute;
  position-fallback: --compass;
}
@position-fallback --compass {
  @try {
    bottom: anchor(--anchor top);
    left: anchor(--anchor right);
  }
  @try {
    top: anchor(--anchor bottom);
    left: anchor(--anchor right);
  }
}

如要查看規格,請按這裡。這個 API 也會提供 Polyfill。

範例

您已瞭解彈出式視窗的用途和運作方式,接下來我們來深入探討一些範例。

通知

這個範例會顯示「複製到剪貼簿」通知。

  • 使用 [popover=manual]
  • 在動作中顯示含有 showPopover 的彈出式視窗。
  • 2000ms 逾時後,請使用 hidePopover 隱藏。

祝酒

這個範例會使用頂層顯示浮動式訊息樣式的通知。

  • 類型為 manual 的其中一個快顯視窗會做為容器。
  • 新通知會附加至彈出式視窗,並顯示彈出式視窗。
  • 點選時,系統會使用 Web Animations API 移除這些元素,並從 DOM 中移除。
  • 如果沒有要顯示的訊息,系統會隱藏彈出式視窗。

巢狀選單

這個範例展示巢狀導覽選單的運作方式。

  • 請改用 [popover=auto],因為這個元素允許巢狀彈出式視窗。
  • 使用每個下拉式選單的第一個連結上的 autofocus,即可透過鍵盤導覽。
  • 這非常適合使用 CSS Anchoring API。不過,在本示範中,您可以使用少量 JavaScript,透過自訂屬性更新位置。
const ANCHOR = (anchor, anchored) => () => {
  const { top, bottom, left, right } = anchor.getBoundingClientRect();
  anchored.style.setProperty("--top", top);
  anchored.style.setProperty("--right", right);
  anchored.style.setProperty("--bottom", bottom);
  anchored.style.setProperty("--left", left);
};

PRODUCTS_MENU.addEventListener("popovershow", ANCHOR(PRODUCT_TARGET, PRODUCTS_MENU));

請注意,由於本示範使用 autofocus,因此必須以「全螢幕檢視」模式開啟,才能使用鍵盤導覽。

媒體彈出式視窗

這部示範影片說明如何彈出媒體。

  • 使用 [popover=auto] 輕觸即可關閉。
  • JavaScript 會監聽影片的 play 事件,並彈出影片。
  • 彈出式視窗的 popoverhide 事件會暫停影片。

Wiki 樣式彈出式視窗

這個範例展示如何建立含有媒體的內嵌內容工具提示。

  • 使用[popover=auto]。顯示其中一個會隱藏其他項目,因為這些項目並非祖先。
  • 使用 JavaScript 在 pointerenter 上顯示。
  • 這是 CSS Anchoring API 的另一個完美候選項目。

本示範會使用快顯視窗建立導覽匣。

  • 使用 [popover=auto] 輕觸即可關閉。
  • 使用 autofocus 將焦點放在第一個導覽項目。

管理背景

這個範例展示如何管理多個快顯視窗的背景,只顯示一個 ::backdrop

  • 使用 JavaScript 維護顯示的彈出式視窗清單。
  • 將類別名稱套用至頂層最低的快顯視窗。

自訂游標彈出式視窗

這個範例會說明如何使用 popovercanvas 升級至頂層,並用來顯示自訂游標。

  • 使用 showPopover[popover=manual]canvas 升級至頂層。
  • 開啟其他彈出式視窗時,請隱藏並顯示 canvas 彈出式視窗,確保它位於最上層。

動作表彈出式視窗

本範例展示如何將快顯視窗當做動作表單使用。

  • 預設顯示的彈出式視窗會覆寫 display
  • 系統會透過彈出式視窗觸發器開啟動作表。
  • 顯示時,快顯視窗會升級至頂層,並轉換為檢視畫面。
  • 輕觸即可返回。

鍵盤啟動的彈出式視窗

這個範例展示如何使用 Popover 建立指令區塊樣式的 UI。

  • 使用 cmd + j 顯示彈出式視窗。
  • input 會以 autofocus 為焦點。
  • 下拉式方塊是位於主要輸入內容下方的第二個 popover
  • 如果沒有下拉式選單,輕觸即可關閉調色盤。
  • Anchoring API 的另一個候選項目

計時彈出式視窗

這個範例會在四秒後顯示閒置彈出式視窗。應用程式通常會使用這種 UI 模式,在含有使用者安全資訊時顯示登出強制回應。

  • 使用 JavaScript 在一段時間沒有活動後顯示彈出式視窗。
  • 在顯示的快訊中重設計時器。

螢幕保護程式

與先前的試用版類似,您可以在網站中加入一絲奇想,並新增螢幕保護程式。

  • 使用 JavaScript 在一段時間沒有活動後顯示彈出式視窗。
  • 輕觸即可隱藏及重設計時器。

文字插入點追蹤

這個範例展示如何讓快顯視窗跟隨輸入插入號。

  • 根據選取項目、重要事件或特殊字元輸入內容顯示彈出式視窗。
  • 使用 JavaScript 和範圍限定的自訂屬性,更新快顯視窗位置。
  • 這種模式需要仔細思考要顯示的內容和無障礙功能。
  • 這類 UI 經常出現在文字編輯 UI 和可標記的應用程式中。

懸浮動作按鈕選單

本示範說明如何使用 Popover 實作懸浮動作按鈕選單,不必使用 JavaScript。

  • 使用 showPopover 方法宣傳 manual 類型的快顯視窗。這是主要按鈕。
  • 選單是另一個目標為主要按鈕的彈出式視窗。
  • 選單已開啟 popovertoggletarget
  • 使用 autofocus 將焦點移至顯示的第一個選單項目。
  • 輕觸關閉會關閉選單。
  • 圖示扭曲效果使用 :has()。如要進一步瞭解 :has(),請參閱這篇文章

就是這麼簡單!

以上就是對 Popover 的簡介,這項功能將在 Open UI 計畫中推出。只要善用這項功能,就能為網頁平台帶來絕佳的體驗。

請務必查看 Open UI。隨著 API 演進,快顯說明也會隨之更新。如要查看所有示範內容,請前往這個集合

感謝你來訪!