关于如何使用 <dialog>
元素构建颜色自适应、响应式且无障碍的迷你和大型模态框的基础概述。
在这篇文章中,我想分享我对如何使用 <dialog>
元素构建颜色自适应、响应式且无障碍的迷你和大型模态框的想法。 试用演示 并查看源代码!
如果您更喜欢视频,这是这篇文章的 YouTube 版本
概述
<dialog>
元素非常适合页面内上下文信息或操作。考虑一下,当用户体验可以从同一页面操作而不是多页面操作中获益时:也许是因为表单很小,或者用户唯一需要执行的操作是确认或取消。
<dialog>
元素最近在各个浏览器中变得稳定
我发现该元素缺少一些东西,因此在这个 GUI 挑战 中,我添加了我期望的开发者体验项目:额外的事件、轻量级关闭、自定义动画以及迷你和大型类型。
标记
<dialog>
元素的基本要素很简单。该元素将自动隐藏,并内置样式以覆盖您的内容。
<dialog>
…
</dialog>
我们可以改进这个基线。
传统上,对话框元素与模态框有很多共同之处,并且通常名称可以互换。 我在这里自由地将对话框元素用于小型对话框弹出窗口(迷你)以及全页对话框(大型)。 我将它们命名为大型和迷你,两种对话框都针对不同的用例进行了轻微调整。 我添加了一个 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 键关闭
重要的是要使其易于关闭这个可能具有干扰性的元素。 幸运的是,对话框元素将为您处理 Esc 键,从而使您摆脱编排负担。
样式
样式化对话框元素有一条简单的路径和一条困难的路径。 简单的路径是通过不更改对话框的 display 属性并使用其限制来实现的。 我走了一条艰难的道路,为打开和关闭对话框提供自定义动画,接管 display
属性等等。
使用 Open Props 设置样式
为了加速自适应颜色和整体设计一致性,我无耻地引入了我的 CSS 变量库 Open Props。 除了免费提供的变量外,我还导入了一个 normalize 文件和一些 按钮,Open Props 都将它们作为可选导入提供。 这些导入帮助我专注于自定义对话框和演示,而无需大量样式来支持它并使其看起来不错。
样式化 <dialog>
元素
拥有 display 属性
对话框元素的默认显示和隐藏行为将 display 属性从 block
切换为 none
。 不幸的是,这意味着它无法在内部和外部进行动画处理,只能在内部进行动画处理。 我想在内部和外部都进行动画处理,第一步是设置我自己的 display 属性
dialog {
display: grid;
}
通过更改并因此拥有 display 属性值,如上面的 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;
}
样式化附加项
我将此部分称为“附加项”,因为它更多地与我的对话框元素演示有关,而不是与一般的对话框元素有关。
滚动包含
当显示对话框时,用户仍然可以滚动其后面的页面,这是我不希望发生的
通常,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
的包装 flexbox 布局,以在按钮之间提供空间。 菜单元素具有诸如 <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
属性。 之前,本指南将 display 设置为 grid,并且永远不会将其设置为 none。 这解锁了在内部和外部进行动画处理的能力。
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 可以检索此字符串,以深入了解对话框是如何关闭的。 您会发现,每次我从各种按钮调用该函数时,我也提供了关闭字符串,以便为我的应用程序提供有关用户交互的上下文。
添加 closing 和 closed 事件
对话框元素附带一个 close 事件:当调用对话框 close()
函数时,它会立即发出。 由于我们正在为此元素设置动画,因此最好在动画之前和之后都有事件,以便更改以抓取数据或重置对话框表单。 我在此处使用它来管理关闭对话框上 inert
属性的添加,在演示中,我使用这些事件在用户提交新图像时修改头像列表。
为了实现这一点,创建两个名为 closing
和 closed
的新事件。 然后侦听对话框上的内置 close 事件。 从此处,将对话框设置为 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。 这就是为什么 dialogClose
是一个 async 函数 的原因; 然后它可以 await
返回的 Promise 并自信地前进到 closed 事件。
添加 opening 和 opened 事件
由于内置对话框元素未提供像 close 那样的 open 事件,因此这些事件不太容易添加。 我使用 MutationObserver 来深入了解对话框属性的更改。 在此观察器中,我将监视 open 属性的更改并相应地管理自定义事件。
与我们启动 closing 和 closed 事件的方式类似,创建两个名为 opening
和 opened
的新事件。 在我们之前侦听对话框 close 事件的地方,这次使用创建的 mutation observer 来监视对话框的属性。
…
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)
}
})
})
当对话框属性更改时,将调用 mutation observer 回调函数,并将更改列表作为数组提供。 迭代属性更改,查找要打开的 attributeName
。 接下来,检查元素是否具有该属性:这会通知对话框是否已打开。 如果已打开,请删除 inert
属性,将焦点设置为请求 autofocus
的元素或对话框中找到的第一个 button
元素。 最后,类似于 closing 和 closed 事件,立即分派 opening 事件,等待动画完成,然后分派 opened 事件。
添加 removed 事件
在单页应用程序中,对话框通常根据路由或其他应用程序需求和状态添加和删除。 当对话框被删除时,清理事件或数据可能很有用。
您可以使用另一个 mutation observer 来实现此目的。 这次,我们不是观察对话框元素上的属性,而是观察 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)
}
})
})
})
每当从文档正文中添加或删除子元素时,都会调用 mutation observer 回调。 被监视的特定 mutation 是针对具有对话框 nodeName
的 removedNodes
。 如果删除了对话框,则会删除 click 和 close 事件以释放内存,并分派自定义 removed 事件。
删除 loading 属性
为了防止对话框动画在添加到页面或页面加载时播放其退出动画,已向对话框添加了 loading 属性。 以下脚本等待对话框动画完成运行,然后删除该属性。 现在对话框可以自由地在内部和外部进行动画处理,并且我们有效地隐藏了原本会分散注意力的动画。
export default async function (dialog) {
…
await animationsComplete(dialog)
dialog.removeAttribute('loading')
}
在此处了解有关 防止页面加载时运行 CSS 关键帧动画 问题的更多信息。
全部放在一起
这是完整的 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()
}
}
在我使用对话框元素构建的演示中,我使用 closed 事件和表单数据向列表添加新的头像元素。时机把握得很好,对话框已完成退出动画,然后一些脚本会动画显示新的头像。 благодаря 新事件,协调用户体验可以更加顺畅。
注意 dialog.returnValue
:这包含在调用对话框 close()
事件时传递的关闭字符串。在 dialogClosed
事件中,了解对话框是被关闭、取消还是确认至关重要。如果是确认,则脚本会抓取表单值并重置表单。重置非常有用,这样当对话框再次显示时,它是空白的,并为新的提交做好准备。
结论
既然您知道我是如何做到的,您会怎么做呢? 🙂
让我们使我们的方法多样化,并学习在 Web 上构建的所有方法。
创建一个演示,在 Twitter 上给我发推文 链接,我会将其添加到下面的社区混音部分!
社区混音
- @GrimLink 的 3 合 1 对话框。
- @mikemai2awesome 的 一个不错的混音,它不会更改
display
属性。 - @geoffrich_ 的 Svelte 和不错的 Svelte FLIP 润色。