Html Popover

學習Blog
11 min readOct 28, 2023

--

https://www.naiveui.com/en-US/os-theme/components/message

最近前端開發常常遇到需要設定彈窗的需求,但彈窗之間會互相遮蓋,所以研究了 dialogpopover 來解決這個問題。這邊紀錄 popover 的使用方法

popover 分兩種,modal 與 non-modal,差別是前者不允許跟 modal 以外的物件互動,後者可以。

用 popover api 建立的 Popover 都是 non-modal。如果想要建立 modal, 使用 <dialog> tag,但注意 <dialog> tag 預設顯示並不是在 top layer之上,而 popover 則是。兩者有很明顯相似之處,例如你可能想建立一個不能被關閉的 popover,用 declarative HTML 操作。你也可以將 dialog 轉換成 popover,但能同時用 popover 操作與擁有 dialog 的特徵

建立 popover 方式

  1. 直接在 html 上宣告
<button popovertarget="mypopover">Toggle the popover</button>
<div id="mypopover" popover>Popover content</div>

2. 用 JS API ,例如 HTMLElement.togglePopover()

一般來說一個 tag 加上 popover 屬性就會變成 popover,預設是用 display: none 隱藏;另外 popover 預設值是 auto

<div id="mypopover" popover>Popover content</div>

可以改變 popovertargetaction 操作 popover 行為,共有 “hide”, “show”, or “toggle” 三種。例如下面程式碼,點選 show 只會讓 popover 顯示

<button popovertarget="mypopover" popovertargetaction="show">
Show popover
</button>
<button popovertarget="mypopover" popovertargetaction="hide">
Hide popover
</button>
<div id="mypopover" popover>Popover content</div>

另外 popovertargetaction 預設值是 "toggle";當 popover 顯示時,會移除 display: none,並位於 top layer 上置頂

Auto 狀態與 light dismiss

popover element 有popoverpopover="auto" 屬性,有幾個特徵

  1. light dismissed:點選 popover 外或 esc 按鍵就就可以關閉 popover
  2. 一次只能有一個 popover,當第二個出現就隱藏第一個,只有巢狀 auto popovers 是例外
  3. 其他元素成功呼叫 HTMLDialogElement.showModal() 和 Element.requestFullscreen() 時會關閉使用 popover="auto" 的彈出框
  4. Auto state 適合用來一次只顯示一個 popover,目的是減少一次顯示多個 popover 造成混淆、混亂,或者用最新狀況來覆蓋之前的 popover

Using manual popover state

<div id="mypopover" popover="manual">Popover content</div>

manual 屬性的 popover 有幾個特徵

  1. 不再是 light dismissed,但 show/hide/toggle 屬性按鈕依然可用
  2. popover 彼此之間獨立,可以同時顯示

Showing popovers via JavaScript

HTMLElement.popover 屬性可以用來取得或設定 popover attribute,可以透過 JS 藉此創造 popover,或檢查是否有 popover 功能

function supportsPopover() {
return HTMLElement.prototype.hasOwnProperty("popover");
}

HTMLButtonElement.popoverTargetElementHTMLInputElement.popoverTargetElement 等同於 popovertarget 屬性,決定控制的 popover element

HTMLButtonElement.popoverTargetActionHTMLInputElement.popoverTargetAction 等同於 popovertargetaction屬性,決定按鈕動作

整合上面的資訊,可以用 JS 寫出一個操作方法

const popover = document.getElementById("mypopover");
const toggleBtn = document.getElementById("toggleBtn");

const keyboardHelpPara = document.getElementById("keyboard-help-para");

const popoverSupported = supportsPopover();

if (popoverSupported) {
popover.popover = "auto";
toggleBtn.popoverTargetElement = popover;
toggleBtn.popoverTargetAction = "toggle";
} else {
toggleBtn.style.display = "none";
}

另外也可透過按鍵操作

document.addEventListener("keydown", (event) => {
if (event.key === "h") {
if (popover.matches(":popover-open")) {
popover.hidePopover();
}
}
  if (event.key === "s") {
if (!popover.matches(":popover-open")) {
popover.showPopover();
}
}
});

透過 Element.matches() 檢查 popover 有沒有該偽元素,確認是否有開啟;或者能更簡潔點用 togglePopover 切換

document.addEventListener("keydown", (event) => {
if (event.key === "h") {
popover.togglePopover();
}
});

Dismissing popovers automatically via a timer

popover 應用中,有個情況是顯示一陣子後消失,例如 toast 通知,常見情境是使用者一次執行多個行為而產生通知,像是大量上傳檔案,個別顯示是否上傳成功或失敗等,這個情境可以使用手動顯示的 popover,並用 setTimeout 關閉 popover

function makeToast(result) {
const popover = document.createElement("article");
popover.popover = "manual";
popover.classList.add("toast");
popover.classList.add("newest");

let msg;

if (result === "success") {
msg = "Action was successful!";
popover.classList.add("success");
successCount++;
} else if (result === "failure") {
msg = "Action failed!";
popover.classList.add("failure");
failCount++;
} else {
return;
}

popover.textContent = msg;
document.body.appendChild(popover);
popover.showPopover();

updateCounter();

setTimeout(() => {
popover.hidePopover();
popover.remove();
}, 4000);
}

popover 有提供事件 beforetoggle 讓使用者決定 popover 在顯示/關閉後,接著要做什麼,該event 有 event.oldState、event.newState 可以知道動作執行前後的狀態

popover.addEventListener("toggle", (event) => {
if (event.newState === "open") {
moveToastsUp();
}
});

function moveToastsUp() {
const toasts = document.querySelectorAll(".toast");

toasts.forEach((toast) => {
if (toast.classList.contains("newest")) {
toast.style.bottom = `5px`;
toast.classList.remove("newest");
} else {
const prevValue = toast.style.bottom.replace("px", "");
const newValue = parseInt(prevValue) + 50;
toast.style.bottom = `${newValue}px`;
}
});
}

Nested popovers

雖然 auto popover 一次只能顯示一個,但有個例外是 popover 裡面再顯示 popover 的巢狀 popover,例如一個選單內有多個選項 ( nested popover menus )

巢狀 popover 有三種

  1. DOM 結構父子關係
<div popover>
Parent
<div popover>Child</div>
</div>
  1. 內部 elements 操作 popover
<div popover>
Parent
<button popovertarget="foo">Click me</button>
</div>

<div popover id="foo">Child</div>
  1. 透過 anchor 屬性
<div popover id="foo">Parent</div>
<div popover anchor="foo">Child</div>

Styling popovers

如果要對 popover 用 css 調整樣式,可以使用 [popover],也就是含有 popover 屬性的 element,或者可以用 :popover-open 針對已經開啟的 popover 調整

// 這些都是 popover 預設樣式
[popover] {
position: fixed;
inset: 0;
width: fit-content;
height: fit-content;
margin: auto;
border: solid;
padding: 0.25em;
overflow: auto;
color: CanvasText;
background-color: Canvas;
}

如果想針對樣式、顏色調整,需要針對 popover 預設樣式調整

:popover-open {
width: 200px;
height: 100px;
position: absolute;
inset: unset;
bottom: 5px;
right: 5px;
margin: 0;
}

另外 popover 有跟 dialog 一樣可以擁有 ::backdrop 偽元素,顯示時可以藉由調整這個屬性指定 popover 背景樣式

--

--

學習Blog
學習Blog

No responses yet