基本介紹如何使用 <dialog>
元素建構自動調整亮度、回應式,以及方便存取的迷你和大型模組。
在這篇文章中,我想分享您使用 <dialog>
元素建構自動調整色彩、回應式,以及無障礙迷你和大型視窗的想法。試用示範並查看原始碼!
如果你偏好使用影片,也可以觀看這篇 YouTube 文章:
總覽
<dialog>
元素適用於網頁內內容資訊或動作。請考慮在何時可對單一網頁動作 (而非多頁動作) 提供良好的使用者體驗:也許是因為表單過小,或者使用者只需要確認或取消。
<dialog>
元素最近在各瀏覽器上變得穩定:
我發現該元素缺少部分內容,因此在 GUI 挑戰中,我新增了預期的開發人員體驗項目:額外事件、輕關閉、自訂動畫,以及迷你和大型相片類型。
標記
<dialog>
元素的基本要素不大。系統會自動隱藏該元素,並內建多種樣式來疊加內容。
<dialog>
…
</dialog>
我們可以改善這個基準。
一般來說,對話方塊元素會與強制回應模組共用大量,且名稱經常可互換。我決定將對話方塊元素用於小型對話方塊 (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>
超級對話
大型對話方塊包含以下三個元素:<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 鍵結束
請務必輕易關閉這個可能會造成乾擾的元素。幸好,對話方塊元素會為您處理逸出鍵,讓您免於自動化調度管理的負擔。
風格
要設定對話方塊元素樣式和固定路徑樣式,方法也有很多。使用簡易路徑的方法是不變更對話方塊的顯示屬性,並遵守其限制。我會使用最困難的路徑提供自訂動畫,用於開啟和關閉對話方塊、接管 display
屬性等。
使用開放屬性設定樣式
為了加速自動調整色彩和整體設計一致性,我對於 CSS 變數程式庫的「Open Props」很瞭解。除了免費提供的變數以外,我也會匯入正規化檔案和一些按鈕,這兩個按鈕都會以選用匯入的形式提供。這些匯入項目可讓我專注於自訂對話方塊和示範,而不需要大量樣式來支援樣式,讓外觀看起來更美觀。
設定 <dialog>
元素的樣式
擁有多媒體廣告屬性
對話方塊元素的預設顯示和隱藏行為會將顯示屬性從 block
切換為 none
。很遺憾,這表示不能在內外動畫。我想要同時為內外的使用者加上動畫效果,第一步是設定自己的 display 屬性:
dialog {
display: grid;
}
如上述 CSS 程式碼片段所示,顯示屬性值已變更且成為擁有者,因此需要管理大量樣式的樣式,以便提供適當的使用者體驗。首先,對話方塊的預設狀態會關閉。您可以以視覺化方式呈現此狀態,並防止對話方塊接收與下列樣式的互動:
dialog:not([open]) {
pointer-events: none;
opacity: 0;
}
現在對話方塊不會顯示,無法在未開啟時與互動。稍後,我們會新增一些 JavaScript 來管理對話方塊中的 inert
屬性,確保鍵盤和螢幕閱讀器使用者也無法存取隱藏對話方塊。
為對話方塊提供自動調整色彩主題
雖然 color-scheme
會將文件設為瀏覽器提供的自動調整色彩主題,以符合淺色和深色的系統偏好設定,但我想自訂對話方塊元素。開啟 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;
}
其他樣式設定
我將這個部分稱為「額外項目」,因為比起一般的對話方塊元素,在對話方塊元素示範中,需要進行更多操作。
捲動隔離
顯示對話方塊時,使用者仍可捲動後面的頁面,但請勿執行以下動作:
通常,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
元素是用來包含對話方塊的動作按鈕。它使用包含 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]
屬性自動化調度管理 1
或 0
。如要在 0% 和 100% 之間轉換,請告知瀏覽器您需要的時間以及您希望的加/減速設定:
dialog {
transition: opacity .5s var(--ease-3);
}
為轉場效果新增動態效果
如果使用者能接受動作,大型對話方塊和迷你對話方塊就應當進入時滑行,並在使用者離開時向外擴充。您可以利用 prefers-reduced-motion
媒體查詢和一些公開屬性來實現:
@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;
}
}
調整行動裝置的結束動畫
先前在樣式一節中,大型對話方塊樣式經過調整,適合行動裝置使用,就像動作表一樣,將一小片從螢幕底部滑入並仍連接至底部。向外擴充動畫並不適合這個新設計,我們可透過以下幾個媒體查詢和一些公開屬性來調整這項設定:
@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
函式也用於建構浮動式訊息元件,會根據動畫是否完成和轉換承諾傳回承諾。這就是為何 dialogClose
是非同步函式的原因,因此可以接著 await
傳回的承諾,並放心移至關閉的事件。
新增開啟和已開啟的活動
這些事件並不容易新增,因為內建的對話方塊元素不會像關閉功能一樣提供開啟事件。我使用 MutationObserver 深入分析對話方塊的屬性變更。在這個觀察器中,我會注意開啟屬性的變更,並據此管理自訂事件。
類似於我們開始和關閉事件的方式,建立兩個名為 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
元素。最後,與關閉和關閉事件類似,請立即傳送開啟事件,等待動畫完成,然後分派開啟的事件。
新增已移除的事件
在單一頁面應用程式中,系統通常會根據路徑或其他應用程式需求和狀態,新增及移除對話方塊。移除對話方塊時,建議您清除事件或資料。
您可以使用其他異動觀察器達成此目的。這次不會觀察對話方塊元素的屬性,而是會觀察主體元素的子項,並留意對話方塊元素遭到移除的情形。
…
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)
}
})
})
})
每當在文件內文中新增或移除子項時,就會呼叫異動觀察器回呼。監控的特定異動適用於具有對話方塊 nodeName
的 removedNodes
。如果對話方塊已移除,系統會移除點擊和關閉事件來釋放記憶體,並分派自訂的移除事件。
移除載入屬性
為了避免對話方塊動畫在新增至頁面或載入網頁時播放的結束動畫,已在對話方塊中新增載入屬性。以下指令碼會等待對話方塊動畫執行完畢,然後移除屬性。現在,對話方塊可以自由加入和跳出動畫,也可以有效隱藏原本造成乾擾的動畫。
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
事件中,請務必瞭解對話方塊是否已關閉、取消或已確認。如果已經確認,指令碼就會擷取表單值並重設表單。重設會很實用,因此在再次顯示對話方塊時,該對話方塊會是空白的,可以準備新的提交內容。
結語
現在既然你已經知道我怎麼做,你會怎麼做‽ 🙂?
讓我們帶您更多元的方法,並瞭解運用網路打造網站的所有方式。
請建立示範並透過 Twitter 推文連結,我就能將這項工具新增至下方的「社群重混」部分!
社群重混作品
- @GrimLink 及 3-in-1 對話方塊。
- @mikemai2awesome 搭配不會變更
display
屬性的優質重混作品。 - @geoffrich_:搭配 Svelte 和好的 Svelte FLIP 團隊。
資源
- GitHub 上的原始碼
- Doodle 顯示圖片