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

開放式 UI 計畫的目標是協助開發人員輕鬆提供優質使用者體驗。為此,我們試圖處理開發人員遇到的更多問題模式。實際做法是提供更完善的平台內建 API 和元件。

彈出式視窗之一就是彈出式視窗,在「開啟使用者介面」中描述為「彈出式視窗」。

彈出式視窗長期以來都相當惡名昭彰。部分原因是他們建構和部署應用程式的方式。要營造良好形象,這並不容易,但只要將使用者帶往特定內容,或是讓他們注意到網站上的內容 (尤其是在展現巧思的情況下使用),就可以帶來大量價值。

建立彈出式視窗時,通常需要兩個主要疑慮:

  • 如何確保放在適當位置的排名。
  • 如何提升易用性 (鍵盤好上手、可聚焦等)。

內建的 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 對話方塊元素。您必須在「Modal」中使用表單中要求的資訊。而且必須搭配 JavaScript 才能使用。

Dialog.showModal();

關於無障礙功能的考量,舉例來說,如要為 Safari 15.4 以下版本的使用者提供服務,建議使用 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 屬性會定義從彈出式視窗回應元素上,懸停在元素上和結束的時間範圍。實驗的預設行為是彈出的 :hover 之後顯示了彈出式視窗。0.5s這時你需要關閉閃光燈,或需要開啟另一個彈出式視窗才能關閉 (稍後會進一步說明)。這是因為彈出式視窗隱藏時間設為 Infinity

在此同時,您可以使用 JavaScript 來 polyfill 這項功能。

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 等子元素的屬性。
    • anchor 屬性有關 (在開發中的 CSS Anchoring API 中)。
  • 光線關閉。
  • 開啟後,系統會關閉其他非祖系彈出式視窗的彈出式視窗。觀看下方的示範影片,瞭解如何運用祖系彈出式視窗建立巢狀結構。瞭解如何將部分 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-haspopuparia-* 屬性。

如要管理焦點,你可以使用自動對焦屬性,將焦點移至彈出式視窗中的元素。這與對話方塊相同,但返回焦點時會出現差異,這是因為系統會關閉光線。在大多數情況下,關閉彈出式視窗後,焦點會回到先前聚焦的元素。但焦點會移至淺色關閉上所點選的元素 (如果可以聚焦)。請參閱說明文件的焦點管理相關章節

您需要開啟「全螢幕版本」看看效果如何

在本示範中,聚焦的元素會顯示綠色外框。試試看用鍵盤在介面各處分頁。請注意當彈出式視窗關閉時,焦點會傳回的位置。你可能也會發現,如果按下 Tab 鍵,彈出式視窗就會關閉。這是刻意安排的雖然彈出式視窗可以管理焦點,但並不代表焦點。當焦點從彈出式視窗移出時,鍵盤導覽功能會找出關閉訊號。

錨定 (開發中)

就彈出視窗而言,有個難以因應的模式,就是將元素固定在觸發條件。舉例來說,假設將工具提示設為顯示在觸發程序上方,但文件遭捲動。工具提示可能會遭可視區域截斷。目前有可處理的 JavaScript 項目,例如「浮動式使用者介面」。他們會調整工具提示的位置,讓您停止這個情形,並且依賴想要的位置順序。

但是,我們希望您能用自己的樣式來定義。除了 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 的彈出視窗可當做容器使用。
  • 新通知會附加至彈出式視窗,並顯示彈出式視窗。
  • 按下時則會透過網頁動畫 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 事件會暫停影片。

威基風格流行樂

以下示範說明如何建立包含媒體的內嵌內容工具提示。

  • 使用[popover=auto]。顯示其中一人不會遮住其他組織,因為他們不是祖先。
  • 透過 JavaScript 顯示在 pointerenter 上。
  • 另一個最適合使用 CSS Anchoring API 的方法。

這項示範會使用彈出式視窗建立導覽匣。

  • 使用 [popover=auto] 關閉燈光。
  • 使用 autofocus 將焦點移至第一個導覽項目。

管理背景幕

本示範示範如何管理多個彈出式視窗的背景,但這些彈出式視窗只需要顯示一個 ::backdrop

  • 使用 JavaScript 來維護可見的彈出式視窗清單。
  • 將類別名稱套用至頂端圖層最低的彈出。

自訂遊標彈出式視窗

本示範說明如何使用 popovercanvas 升級為頂層圖層,並用來顯示自訂遊標。

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

操作單彈出式視窗

以下示範說明如何使用彈出式視窗做為操作單。

  • 預設顯示彈出式視窗,覆寫 display
  • 已透過彈出式視窗觸發條件開啟 Actionsheet。
  • 此彈出式視窗顯示時,會升級為頂層圖層,並轉譯成檢視畫面。
  • 光線關閉可用來返回。

鍵盤已啟用彈出式視窗

本示範示範如何將彈出式視窗用於指令調色盤樣式 UI。

  • 使用 cmd + j 鍵顯示彈出式視窗。
  • input 目前聚焦於 autofocus
  • 組合方塊是位於主要輸入欄位下方的第二個 popover
  • 如果沒有下拉式選單,則光源關閉會關閉調色盤。
  • Anchoring API 的另一個候選項目

計時性彈出式視窗

此示範顯示四秒後顯示閒置的彈出式視窗。通常用於保留使用者安全資訊的應用程式使用的 UI 模式,以顯示登出互動視窗。

  • 使用 JavaScript 在閒置一段時間後顯示彈出式視窗。
  • 在彈出式視窗顯示時重設計時器。

螢幕保護程式

與上一個示範類似,您可以在網站中加入螢幕保護程式,

  • 使用 JavaScript 在閒置一段時間後顯示彈出式視窗。
  • 關閉可隱藏及重設計時器。

文字追蹤

此示範是如何隨著輸入插入點接續的彈出式視窗。

  • 根據選取項目、按鍵事件或特殊字元顯示彈出式視窗。
  • 使用 JavaScript 以限定範圍的自訂屬性更新彈出位置。
  • 因此必須考量內容的顯示和無障礙程度。
  • 在文字編輯 UI 和應用程式中,通常用於標記。

懸浮動作按鈕選單

本示範示範如何使用彈出式視窗,在不使用 JavaScript 的情況下實作懸浮動作按鈕選單。

  • 使用 showPopover 方法升級 manual 類型彈出式視窗。這是主要按鈕。
  • 選單是主按鈕目標的另一個彈出式視窗。
  • 已開啟「popovertoggletarget」選單。
  • 使用 autofocus 即可將焦點移至節目上的第一個選單項目。
  • 光線關閉會關閉選單。
  • 轉動圖示表示使用 :has()。如要進一步瞭解 :has(),請參閱這篇文章

這樣就大功告成了!

以上就是彈出式視窗簡介,這是「開放式 UI 計畫」中預計會加入的計畫。而且大概會因為使用這項技術,成為網路平台的絕佳利器。

請務必查看開啟 UI彈出式視窗說明會隨著 API 發展持續更新。以下是所有示範的系列作品

感謝你透過「彈出」訊息!


相片來源:Madison Oren,發表於 Unsplash 網站上