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

開放式 UI 計畫的目標是讓開發人員能更輕鬆地提供優質的使用者體驗。為了達成這個目標,我們正在努力處理開發人員面臨的問題模式更多。我們可以提供更完善的平台內建 API 和元件。

在開放式 UI 中,彈出式視窗就是彈出式視窗,這類問題稱為「彈出式視窗」。

長久以來,彈出式視窗的聲譽相當普遍。而部分原因是它們的建構和部署方式。製作 Shorts 並不容易,但引導使用者前往某些事物,或讓他們注意到你的網站內容,這種做法能帶來許多價值,如果比較適合習慣,更是如此。

建立彈出式視窗時經常有兩個主要疑慮:

  • 如何確保素材資源顯示在其他內容上方的適當位置。
  • 如何讓文字容易使用 (鍵盤方便、可聚焦等等)。

內建的 Popover API 有各種目標,所有的目標都相同,就是讓開發人員能輕鬆建立這種模式。值得注意的目標如下:

  • 方便使用者在文件的其他部分上方顯示元素及其子系。
  • 打造容易理解的內容。
  • 針對最常見的行為 (淺色關閉、單例模式、堆疊等),不需要使用 JavaScript。

您可以在 OpenUI 網站上查看彈出式視窗的完整規格。

瀏覽器相容性

現在可以在哪裡使用內建的 Popover API?這項功能在 Chrome Canary 中支援於本文撰寫的「實驗性網路平台功能」旗標後方。

如要啟用該標記,請開啟 Chrome Canary 並前往 chrome://flags。接著啟用「Experimental web platform features」旗標。

此外,開發人員也可以申請來源試用,以便在正式環境中體驗這項功能。

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

您可以透過以下方式使用彈出式視窗支援:

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

目前的解決方案

你目前可以透過什麼方式宣傳自己的內容?如果瀏覽器支援「HTML 對話方塊」元素,您可以使用 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 將該項功能化。

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-haspopup 這類 aria-* 屬性。

如要管理焦點,可以使用自動對焦屬性,將焦點移到彈出式視窗中的元素。這與對話方塊相同,但傳回焦點時會有所不同,原因是關閉。在大多數情況下,關閉彈出式視窗會將焦點移回先前聚焦的元素。但如果焦點可以提升焦點,焦點就會移到點按的元素上。請參閱說明中的聚焦管理章節

您需要開啟此試用版的「全螢幕版本」,才能看到正常運作。

在這個示範中,焦點所在的元素會顯示綠色外框。你可以使用鍵盤在介面周圍使用 Tab 鍵。請注意,關閉彈出式視窗時,焦點會回傳的位置。您可能也會發現,如果您使用分頁分頁,彈出式視窗會關閉。這是刻意安排的。雖然彈出式視窗有焦點管理,但他們並沒有掌控焦點。當焦點從彈出式視窗移開時,鍵盤導覽功能會識別關閉訊號。

錨定 (開發中)

說明彈出式視窗時,能夠滿足特殊的特殊設計模式,就是將元素錨定在觸發條件中。舉例來說,如果將工具提示設為顯示在觸發條件上方,但文件遭到捲動,就會發生這種情況。該工具提示可能會被可視區域截斷。目前您可以利用 JavaScript 提供的產品處理這類問題,例如「浮動式使用者介面」。這些人員會調整工具提示的位置,以停止這項操作,並依賴所需位置順序來停止作業。

但我們希望您可以使用自己的樣式定義這個項目。此外,有一個開發中的隨附 API 與 Popover 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
  • 系統透過彈出式視窗觸發條件開啟動作表。
  • 彈出式視窗顯示時,會升級為頂層圖層,並轉譯為檢視畫面。
  • 淺色關閉功能則可用來傳回。

鍵盤已啟用的彈出式視窗

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

  • 按下 cmd + j 鍵即可顯示彈出式視窗。
  • inputautofocus 為焦點。
  • 下拉式方塊是位於主要輸入項目下方的第二個 popover
  • 如未顯示下拉式選單,則燈光關閉會關閉調色盤。
  • 錨定 API 的另一個候選項目

計時性彈出式視窗

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

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

螢幕保護程式

與先前的示範類似,您可以在網站中加入一些逗趣的內容,並加入螢幕保護程式。

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

跳躍追蹤

本示範可如何依輸入插入點顯示彈出式視窗。

  • 根據選取項目、重要事件或特殊字元輸入內容顯示彈出式視窗。
  • 使用 JavaScript 即可透過限定範圍的自訂屬性更新彈出位置。
  • 這種模式需要審慎考量正在呈現及呈現的內容。
  • 通常是在文字編輯 UI 和可加上標記的應用程式中。

懸浮動作按鈕選單

在這個示範影片中,您可以瞭解如何使用彈出式視窗來在沒有 JavaScript 的情況下實作懸浮動作按鈕選單。

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

這樣就大功告成了!

因此,這份簡介將進一步介紹「開放式 UI 計畫」。如果運用得當,將成為網路平台的一大利器。

請務必查看開啟 UI彈出式說明會隨著 API 的改進而更新。這裡提供所有示範的系列作品

感謝你的「爆紅」!


相片來源:Madison OrenUnsplash 網站上