基礎概念總覽:如何使用 <dialog>
元素建構可適應顏色、具備回應式設計且符合無障礙規範的迷你和巨型模式。
在這篇文章中,我想分享我對如何使用 <dialog>
元素建構可自動調整色彩、具備回應性且無障礙的迷你和大型模式的看法。試用示範模式,並查看來源!
如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:
總覽
<dialog>
元素非常適合用於網頁內文脈絡資訊或動作。請考慮何時可使用單一頁面動作,而非多頁面動作,提升使用者體驗:或許是因為表單很小,或使用者只需要確認或取消。
<dialog>
元素最近在各瀏覽器中已趨於穩定:
我發現該元素缺少幾項內容,因此在 GUI Challenge 中,我新增了預期的開發人員體驗項目:額外事件、輕觸即可關閉、自訂動畫,以及迷你和巨型類型。
標記
<dialog>
元素的基本要素不多,元素會自動隱藏,並內建樣式來疊加內容。
<dialog>
…
</dialog>
我們可以改善這個基準。
傳統上,對話方塊元素與模式視窗有許多相似之處,且名稱通常可以互換。我在此擅自使用對話方塊元素,同時處理小型對話方塊彈出視窗 (迷你) 和全頁對話方塊 (特大)。我將這兩個對話方塊分別命名為 mega 和 mini,並根據不同用途稍作調整。我新增了 modal-mode
屬性,可讓您指定類型:
<dialog id="MegaDialog" modal-mode="mega"></dialog>
<dialog id="MiniDialog" modal-mode="mini"></dialog>
不一定,但一般來說,對話方塊元素會用於收集某些互動資訊。對話方塊元素內的表單會一起運作。
建議您使用表單元素包裝對話方塊內容,讓 JavaScript 存取使用者輸入的資料。此外,使用 method="dialog"
的表單內按鈕可以關閉對話方塊,不必使用 JavaScript 即可傳遞資料。
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
…
<button value="cancel">Cancel</button>
<button value="confirm">Confirm</button>
</form>
</dialog>
Mega 對話方塊
表單內有三個巨型對話方塊元素:<header>
、<article>
和 <footer>
。這些是語意容器,也是對話方塊呈現方式的樣式目標。標題會顯示在模式視窗中,並提供關閉按鈕。本文適用於表單輸入內容和資訊。頁尾會保留動作按鈕的 <menu>
。
<dialog id="MegaDialog" modal-mode="mega">
<form method="dialog">
<header>
<h3>Dialog title</h3>
<button onclick="this.closest('dialog').close('close')"></button>
</header>
<article>...</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
第一個選單按鈕具有
autofocus
和 onclick
內嵌事件處理常式。對話方塊開啟時,autofocus
屬性會收到焦點,而我認為最佳做法是將焦點放在取消按鈕上,而不是確認按鈕。這樣可確保確認是刻意操作,而非意外。
迷你對話方塊
迷你對話方塊與巨型對話方塊非常相似,只是缺少 <header>
元素。因此可以縮小並內嵌。
<dialog id="MiniDialog" modal-mode="mini">
<form method="dialog">
<article>
<p>Are you sure you want to remove this user?</p>
</article>
<footer>
<menu>
<button autofocus type="reset" onclick="this.closest('dialog').close('cancel')">Cancel</button>
<button type="submit" value="confirm">Confirm</button>
</menu>
</footer>
</form>
</dialog>
對話方塊元素可做為完整檢視區塊元素的穩固基礎,用於收集資料和使用者互動。這些基本功能可讓您在網站或應用程式中,打造非常有趣且強大的互動體驗。
無障礙設定
對話方塊元素內建的無障礙功能非常完善。我通常會新增這些功能,但這次許多功能都已存在。
還原焦點
如建構側邊導覽列元件一節所述,開啟和關閉項目時,請務必將焦點放在相關的開啟和關閉按鈕上。側邊導覽開啟時,焦點會移至關閉按鈕。按下關閉按鈕時,焦點會還原至開啟該按鈕的按鈕。
使用對話方塊元素時,這是內建的預設行為:
很抱歉,如果您想為對話方塊加入進出動畫,這項功能就會失效。我會在 JavaScript 區段中還原這項功能。
鎖定焦點
對話方塊元素會在文件中為您管理 inert
。在 inert
之前,JavaScript 會用於監看焦點是否離開元素,並在焦點離開時攔截並放回。
inert
之後,文件中的任何部分都可以「凍結」,不再是焦點目標,也無法透過滑鼠互動。焦點不會受限,而是會導向文件中唯一可互動的部分。
開啟並自動對焦元素
根據預設,對話方塊元素會將焦點指派給對話方塊標記中的第一個可聚焦元素。如果這不是使用者預設的最佳元素,請使用 autofocus
屬性。如先前所述,我認為最佳做法是將此項目放在「取消」按鈕上,而不是「確認」按鈕。這樣才能確保確認是刻意操作,而非意外。
使用 Esc 鍵關閉
請務必讓使用者能輕鬆關閉這個可能造成干擾的元素。幸好對話方塊元素會為您處理 Esc 鍵,讓您免除協調負擔。
樣式
您可以輕鬆或困難地為對話方塊元素設定樣式。簡單做法是不變更對話方塊的顯示屬性,並接受其限制。我走的是艱難的路,為開啟和關閉對話方塊提供自訂動畫,接管 display
屬性等。
使用 Open Props 設定樣式
為了加快自適應顏色和整體設計一致性,我毫不避諱地導入了 CSS 變數程式庫 Open Props。除了免費提供的變數,我也匯入 normalize 檔案和一些按鈕,這兩者都是 Open Props 提供的選用匯入項目。這些匯入項目可協助我專注於自訂對話方塊和示範,同時不需要大量樣式支援,就能讓對話方塊看起來美觀。
設定 <dialog>
元素的樣式
擁有顯示屬性
對話方塊元素的預設顯示和隱藏行為,會將顯示屬性從 block
切換為 none
。很抱歉,這表示無法為其進出動畫,只能為其進入動畫。我想製作進場和退場動畫,第一步是設定自己的 display 屬性:
dialog {
display: grid;
}
如上述 CSS 程式碼片段所示,變更並因此擁有顯示屬性值後,您需要管理大量樣式,才能提供適當的使用者體驗。首先,對話方塊的預設狀態為關閉。您可以透過下列樣式,以視覺化方式呈現這個狀態,並防止對話方塊接收互動:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
現在對話方塊處於隱藏狀態,未開啟時無法互動。稍後我會新增一些 JavaScript 來管理對話方塊的 inert
屬性,確保鍵盤和螢幕閱讀器使用者也無法存取隱藏的對話方塊。
為對話方塊提供自動調整色彩主題
雖然 color-scheme
會根據系統偏好設定,為文件選用瀏覽器提供的適應性色彩主題 (淺色或深色),但我希望進一步自訂對話方塊元素。Open Props 提供幾種介面顏色,可自動配合淺色和深色系統偏好設定調整,類似於使用 color-scheme
。這些顏色很適合在設計中建立圖層,我喜歡使用顏色來輔助呈現圖層表面的外觀。背景顏色為 var(--surface-1)
;如要疊加在該圖層上,請使用 var(--surface-2)
:
dialog {
…
background: var(--surface-2);
color: var(--text-1);
}
@media (prefers-color-scheme: dark) {
dialog {
border-block-start: var(--border-size-1) solid var(--surface-3);
}
}
日後會為子項元素 (例如頁首和頁尾) 新增更多適應性色彩。我認為這些是對話方塊元素的額外功能,但對於設計引人入勝且設計良好的對話方塊而言,這些功能非常重要。
回應式對話方塊大小
對話方塊預設會將大小委派給內容,這通常很棒。我的目標是將 max-inline-size
限制在可讀取的大小 (--size-content-3
= 60ch
) 或可視區域寬度的 90%。這樣可確保對話方塊不會在行動裝置上從一側延伸到另一側,也不會在電腦螢幕上過寬而難以閱讀。然後我會新增 max-block-size
,這樣對話方塊就不會超過網頁高度。這也表示我們需要指定對話方塊的可捲動區域,以免對話方塊元素過高。
dialog {
…
max-inline-size: min(90vw, var(--size-content-3));
max-block-size: min(80vh, 100%);
max-block-size: min(80dvb, 100%);
overflow: hidden;
}
請注意,我重複了 max-block-size
兩次。第一個使用 80vh
,這是實體檢視區塊單位。我真正想做的是讓對話方塊保持在相對流程中,以供國際使用者使用,因此我在第二個宣告中使用了邏輯上較新的 dvb
單元 (僅部分支援),等這個單元更穩定後再使用。
大型對話方塊位置
如要協助放置對話方塊元素,建議將其拆解為兩部分:全螢幕背景和對話方塊容器。背景必須遮蓋所有內容,提供陰影效果,以支援這個對話方塊位於前方,且無法存取後方內容。對話方塊容器可自由將自身置中於這個背景上,並根據內容需求採取任何形狀。
下列樣式會將對話方塊元素固定在視窗中,並延展至每個角落,然後使用 margin: auto
將內容置中:
dialog {
…
margin: auto;
padding: 0;
position: fixed;
inset: 0;
z-index: var(--layer-important);
}
行動裝置超大對話方塊樣式
在小型檢視區塊中,我會以稍微不同的方式設定這個全頁大型模式的樣式。我將底部邊界設為 0
,將對話方塊內容帶到可視區域底部。只要稍微調整樣式,就能將對話方塊變成動作表,更貼近使用者的拇指:
@media (max-width: 768px) {
dialog[modal-mode="mega"] {
margin-block-end: 0;
border-end-end-radius: 0;
border-end-start-radius: 0;
}
}
迷你對話方塊的位置
使用較大的可視區域 (例如在桌上型電腦上) 時,我選擇將迷你對話方塊放置在呼叫這些對話方塊的元素上方。我需要 JavaScript 才能執行這項操作。您可以在這裡找到我使用的技術,但我覺得這超出本文範圍。如果沒有 JavaScript,迷你對話方塊會顯示在畫面中央,就像大型對話方塊一樣。
讓圖片更生動
最後,為對話方塊增添一些風格,讓它看起來像遠高於頁面的柔軟表面。只要將對話方塊的邊角設為圓角,即可達到柔和效果。 深度是透過 Open Props 精心製作的陰影屬性達成:
dialog {
…
border-radius: var(--radius-3);
box-shadow: var(--shadow-6);
}
自訂背景虛擬元素
我選擇以非常輕微的方式處理背景,只使用 backdrop-filter
在巨型對話方塊中新增模糊效果:
dialog[modal-mode="mega"]::backdrop {
backdrop-filter: blur(25px);
}
我也選擇在 backdrop-filter
上放置轉場效果,希望瀏覽器日後能允許轉場背景元素:
dialog::backdrop {
transition: backdrop-filter .5s ease;
}
額外樣式
我將這個部分稱為「extras」,因為它與對話方塊元素的一般用途相比,更與對話方塊元素示範有關。
捲動範圍限制
顯示對話方塊時,使用者仍可捲動後方的網頁,這並非我所要的結果:
通常 overscroll-behavior
是我的常用解決方案,但根據規格,這對話方塊並非捲動埠,也就是說,這不是捲動器,因此無法防止任何項目。我可以使用 JavaScript 監看本指南中的新事件 (例如「closed」和「opened」),並在文件中切換 overflow: hidden
,也可以等待 :has()
在所有瀏覽器中穩定運作:
html:has(dialog[open][modal-mode="mega"]) {
overflow: hidden;
}
現在開啟大型對話方塊時,HTML 文件會包含 overflow: hidden
。
「<form>
」版面配置
除了是收集使用者互動資訊的重要元素外,我還會使用它來配置標題、頁尾和文章元素。我打算使用這個版面配置,將文章子項設為可捲動區域。我使用
grid-template-rows
達成這個目標。
文章元素會取得 1fr
,而表單本身的高度上限與對話方塊元素相同。設定這個固定高度和固定列大小,可讓文章元素受到限制,並在溢位時捲動:
dialog > form {
display: grid;
grid-template-rows: auto 1fr auto;
align-items: start;
max-block-size: 80vh;
max-block-size: 80dvb;
}
設定對話方塊 <header>
的樣式
這個元素的作用是為對話方塊內容提供標題,並提供容易找到的關閉按鈕。此外,也提供表面顏色,讓對話方塊文章內容顯示在後方。這些需求會產生 flexbox 容器、垂直對齊的項目 (間距會延伸至邊緣),以及一些邊框間距和間隙,為標題和關閉按鈕預留空間:
dialog > form > header {
display: flex;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
background: var(--surface-2);
padding-block: var(--size-3);
padding-inline: var(--size-5);
}
@media (prefers-color-scheme: dark) {
dialog > form > header {
background: var(--surface-1);
}
}
設定標題關閉按鈕的樣式
由於這個範例使用 Open Props 按鈕,關閉按鈕會自訂為圓形圖示的置中按鈕,如下所示:
dialog > form > header > button {
border-radius: var(--radius-round);
padding: .75ch;
aspect-ratio: 1;
flex-shrink: 0;
place-items: center;
stroke: currentColor;
stroke-width: 3px;
}
設定對話方塊 <article>
的樣式
文章元素在這個對話方塊中扮演特殊角色:如果對話方塊較高或較長,這個空間就會用於捲動。
為達成此目的,父項表單元素已為自身設定一些上限,如果這個文章元素過高,就會受到這些限制。設定 overflow-y: auto
,只在需要時顯示捲軸,並使用 overscroll-behavior: contain
在其中包含捲動功能,其餘則為自訂呈現樣式:
dialog > form > article {
overflow-y: auto;
max-block-size: 100%; /* safari */
overscroll-behavior-y: contain;
display: grid;
justify-items: flex-start;
gap: var(--size-3);
box-shadow: var(--shadow-2);
z-index: var(--layer-1);
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: light) {
dialog > form > article {
background: var(--surface-1);
}
}
設定對話方塊 <footer>
的樣式
頁尾的角色是包含動作按鈕選單。Flexbox 用於將內容對齊頁尾的內嵌軸尾端,然後提供一些間距,讓按鈕有空間。
dialog > form > footer {
background: var(--surface-2);
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
justify-content: space-between;
align-items: flex-start;
padding-inline: var(--size-5);
padding-block: var(--size-3);
}
@media (prefers-color-scheme: dark) {
dialog > form > footer {
background: var(--surface-1);
}
}
設定對話方塊頁尾選單的樣式
menu
元素用於包含對話方塊的動作按鈕。它使用換行 flexbox 版面配置搭配 gap
,在按鈕之間提供空間。選單元素有邊框間距,例如 <ul>
。我也不需要這個樣式,因此一併移除。
dialog > form > footer > menu {
display: flex;
flex-wrap: wrap;
gap: var(--size-3);
padding-inline-start: 0;
}
dialog > form > footer > menu:only-child {
margin-inline-start: auto;
}
動畫
對話方塊元素通常會加入動畫效果,因為它們會進入和離開視窗。 為對話方塊的進入和退出動作提供一些輔助動作,有助於使用者在流程中瞭解自己的位置。
一般來說,對話方塊元素只能以動畫效果顯示,無法隱藏。這是因為瀏覽器會切換元素中的 display
屬性。先前,指南會將顯示畫面設為格線,但絕不會設為無。這樣就能製作進出動畫。
Open Props 隨附許多可用的影格動畫,方便您輕鬆編排及解讀動畫。以下是我採取的動畫目標和分層做法:
- 「減少動態效果」是預設轉場效果,可簡單地淡入和淡出不透明度。
- 如果動作沒問題,系統會新增滑動和縮放動畫。
- 大型對話方塊的回應式行動版面配置已調整為滑出。
安全且有意義的預設轉換
雖然 Open Props 隨附淡入和淡出的關鍵影格,但我偏好這種分層式轉場效果,並將關鍵影格動畫視為潛在升級項目。我們稍早已使用不透明度設定對話方塊的顯示設定,並根據 [open]
屬性協調 1
或 0
。如要在 0% 和 100% 之間轉換,請告知瀏覽器您要的時間長度和緩和類型:
dialog {
transition: opacity .5s var(--ease-3);
}
為轉場效果新增動態效果
如果使用者接受動態效果,大型和迷你對話方塊都應向上滑動做為進入動畫,並縮放做為退出動畫。您可以使用 prefers-reduced-motion
媒體查詢和幾個 Open Props 達成此目的:
@media (prefers-reduced-motion: no-preference) {
dialog {
animation: var(--animation-scale-down) forwards;
animation-timing-function: var(--ease-squish-3);
}
dialog[open] {
animation: var(--animation-slide-in-up) forwards;
}
}
調整行動裝置的退場動畫
在樣式設定一節中,我們已將大型對話方塊樣式調整為更適合行動裝置,使其更像動作功能表,彷彿一小張紙從畫面底部向上滑動,並仍附著在底部。縮放退出動畫不太適合這個新設計,我們可以透過幾個媒體查詢和一些 Open Props 調整:
@media (prefers-reduced-motion: no-preference) and @media (max-width: 768px) {
dialog[modal-mode="mega"] {
animation: var(--animation-slide-out-down) forwards;
animation-timing-function: var(--ease-squish-2);
}
}
JavaScript
您可以使用 JavaScript 新增許多項目:
// dialog.js
export default async function (dialog) {
// add light dismiss
// add closing and closed events
// add opening and opened events
// add removed event
// removing loading attribute
}
這些新增項目是為了實現輕觸即關閉 (點按對話方塊背景)、動畫,以及一些額外事件,以便更準確地取得表單資料。
新增輕觸即關閉功能
這項工作很簡單,非常適合加到未顯示動畫的對話方塊元素。互動方式是監看對話方塊元素上的點擊,並利用事件冒泡評估點擊的項目,且只有在點擊最上層元素時才會close()
:
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
}
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
請注意 dialog.close('dismiss')
。系統會呼叫事件並提供字串。
其他 JavaScript 可以擷取這個字串,深入瞭解對話方塊的關閉方式。您會發現,每次從各種按鈕呼叫函式時,我也提供了近似字串,為應用程式提供使用者互動的背景資訊。
新增結案和已結案事件
對話方塊元素會隨附關閉事件:呼叫對話方塊 close()
函式時,系統會立即發出該事件。由於我們要為這個元素製作動畫,因此最好在動畫前後都有事件,以便擷取資料或重設對話方塊表單。我在這裡使用它來管理已關閉對話方塊中 inert
屬性的新增作業,而在示範中,我使用這些屬性來修改顯示圖片清單 (如果使用者已提交新圖片)。
如要達成這個目標,請建立兩個名為 closing
和 closed
的新事件。然後監聽對話方塊的內建關閉事件。接著,將對話方塊設為 inert
,並傳送 closing
事件。下一個工作是等待對話方塊上的動畫和轉場效果執行完畢,然後傳送 closed
事件。
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
export default async function (dialog) {
…
dialog.addEventListener('close', dialogClose)
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
animationsComplete
函式 (也用於「建構 Toast 元件」) 會根據動畫和轉場效果 Promise 的完成情況,傳回 Promise。因此 dialogClose
是非同步函式,可以 await
傳回的 Promise,並放心地繼續處理關閉事件。
新增開幕和已開放活動
由於內建對話方塊元素不會提供開啟事件 (如同關閉事件),因此這類事件較難新增。我使用 MutationObserver,深入瞭解對話方塊屬性的變化。在這個觀察器中,我會監看 open 屬性的變更,並據此管理自訂事件。
與開始和結束事件的建立方式類似,請建立兩個名為 opening
和 opened
的新事件。先前我們監聽對話方塊關閉事件,這次則使用建立的變異觀察器監看對話方塊的屬性。
…
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
export default async function (dialog) {
…
dialogAttrObserver.observe(dialog, {
attributes: true,
})
}
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
對話方塊屬性變更時,系統會呼叫變動觀察器回呼函式,並以陣列形式提供變更清單。疊代屬性變更,尋找要開啟的 attributeName
。接著,請檢查元素是否具有屬性,這會告知對話方塊是否已開啟。如果已開啟,請移除 inert
屬性,並將焦點設為要求 autofocus
的元素,或對話方塊中找到的第一個 button
元素。最後,與 closing 和 closed 事件類似,請立即傳送 opening 事件,等待動畫完成,然後傳送 opened 事件。
新增已移除的活動
在單頁應用程式中,對話方塊通常會根據路徑或其他應用程式需求和狀態新增及移除。移除對話方塊時,清除事件或資料可能很有用。
你可以使用另一個變動觀察器達成這個目標。這次我們要觀察的不是對話方塊元素上的屬性,而是 body 元素的子項,並監看對話方塊元素是否遭到移除。
…
const dialogRemovedEvent = new Event('removed')
export default async function (dialog) {
…
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
}
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
每當子項新增至文件主體或從中移除時,系統就會呼叫變動觀察工具回呼。要監看的特定突變是針對具有對話方塊 removedNodes
的 nodeName
。如果對話方塊已移除,系統會移除點擊和關閉事件,以釋放記憶體,並傳送自訂移除事件。
移除 loading 屬性
為避免對話方塊動畫在新增至網頁或網頁載入時播放結束動畫,對話方塊已新增載入屬性。下列指令碼會等待對話方塊動畫執行完畢,然後移除屬性。現在對話方塊可以自由進出動畫,我們也有效隱藏了原本會造成干擾的動畫。
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
進一步瞭解如何防止網頁載入時出現關鍵影格動畫。
全部
現在我們已逐一說明各個部分,以下是完整的 dialog.js
:
// custom events to be added to <dialog>
const dialogClosingEvent = new Event('closing')
const dialogClosedEvent = new Event('closed')
const dialogOpeningEvent = new Event('opening')
const dialogOpenedEvent = new Event('opened')
const dialogRemovedEvent = new Event('removed')
// track opening
const dialogAttrObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(async mutation => {
if (mutation.attributeName === 'open') {
const dialog = mutation.target
const isOpen = dialog.hasAttribute('open')
if (!isOpen) return
dialog.removeAttribute('inert')
// set focus
const focusTarget = dialog.querySelector('[autofocus]')
focusTarget
? focusTarget.focus()
: dialog.querySelector('button').focus()
dialog.dispatchEvent(dialogOpeningEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogOpenedEvent)
}
})
})
// track deletion
const dialogDeleteObserver = new MutationObserver((mutations, observer) => {
mutations.forEach(mutation => {
mutation.removedNodes.forEach(removedNode => {
if (removedNode.nodeName === 'DIALOG') {
removedNode.removeEventListener('click', lightDismiss)
removedNode.removeEventListener('close', dialogClose)
removedNode.dispatchEvent(dialogRemovedEvent)
}
})
})
})
// wait for all dialog animations to complete their promises
const animationsComplete = element =>
Promise.allSettled(
element.getAnimations().map(animation =>
animation.finished))
// click outside the dialog handler
const lightDismiss = ({target:dialog}) => {
if (dialog.nodeName === 'DIALOG')
dialog.close('dismiss')
}
const dialogClose = async ({target:dialog}) => {
dialog.setAttribute('inert', '')
dialog.dispatchEvent(dialogClosingEvent)
await animationsComplete(dialog)
dialog.dispatchEvent(dialogClosedEvent)
}
// page load dialogs setup
export default async function (dialog) {
dialog.addEventListener('click', lightDismiss)
dialog.addEventListener('close', dialogClose)
dialogAttrObserver.observe(dialog, {
attributes: true,
})
dialogDeleteObserver.observe(document.body, {
attributes: false,
subtree: false,
childList: true,
})
// remove loading attribute
// prevent page load @keyframes playing
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
使用 dialog.js
模組
模組匯出的函式預期會被呼叫,並傳遞要新增這些新事件和功能的對話方塊元素:
import GuiDialog from './dialog.js'
const MegaDialog = document.querySelector('#MegaDialog')
const MiniDialog = document.querySelector('#MiniDialog')
GuiDialog(MegaDialog)
GuiDialog(MiniDialog)
就這樣,這兩個對話方塊都已升級,可輕觸關閉、修正動畫載入問題,並提供更多事件供您使用。
監聽新的自訂事件
每個升級的對話方塊元素現在都可以監聽五個新事件,如下所示:
MegaDialog.addEventListener('closing', dialogClosing)
MegaDialog.addEventListener('closed', dialogClosed)
MegaDialog.addEventListener('opening', dialogOpening)
MegaDialog.addEventListener('opened', dialogOpened)
MegaDialog.addEventListener('removed', dialogRemoved)
以下是處理這些事件的兩個範例:
const dialogOpening = ({target:dialog}) => {
console.log('Dialog opening', dialog)
}
const dialogClosed = ({target:dialog}) => {
console.log('Dialog closed', dialog)
console.info('Dialog user action:', dialog.returnValue)
if (dialog.returnValue === 'confirm') {
// do stuff with the form values
const dialogFormData = new FormData(dialog.querySelector('form'))
console.info('Dialog form data', Object.fromEntries(dialogFormData.entries()))
// then reset the form
dialog.querySelector('form')?.reset()
}
}
在以對話方塊元素建構的示範中,我使用該關閉事件和表單資料,將新的虛擬人偶元素新增至清單。時機恰到好處,因為對話方塊已完成結束動畫,然後一些指令碼會為新虛擬人偶製作動畫。有了這些新事件,就能更順暢地安排使用者體驗。
注意 dialog.returnValue
:這包含呼叫對話方塊 close()
事件時傳遞的關閉字串。在 dialogClosed
事件中,瞭解對話方塊是否已關閉、取消或確認至關重要。如果確認無誤,指令碼就會抓取表單值並重設表單。重設後,對話方塊再次顯示時就會空白,方便您重新提交。
結論
現在您已瞭解我的做法,您會怎麼做呢?🙂
讓我們多元化地運用各種方法,學習在網路上建構內容。
建立試聽版,然後在推特上傳送連結給我,我會將連結加到下方的社群混音區!
社群重混作品
- @GrimLink,並附上三合一對話。
- @mikemai2awesome 製作的優質混音未變更
display
屬性。 - @geoffrich_,並使用 Svelte 和 Svelte FLIP 潤飾。
資源
- Github 上的原始碼
- Doodle Avatars