下面搬好小板凳,听我好好说说<dialog>元素。
1.<form>元素与对话框自动关闭
对话框元素中往往都会有一两个按钮不参与任何业务逻辑处理,其作用仅仅是关闭对话框,例如Alert类型对话框的确认按钮,或者是对话框右上角的图标字样的关闭按钮。
在过去,这种关闭行为的实现一定是使用JavaScript代码触发的。例如:
或者:
等方法。
实际上,<dialog>元素内置了一种特性,就算不使用JavaScript代码,也能触发对话框的关闭,比如如下所示的HTML代码:
此时,点击“我知道了”按钮,对话框就会立即关闭。
就是这么神奇,不信你可以在浏览器中输入地址https://www.htmlapi.cn/3/2-1.html或扫码访问来体验对应的演示页面。
点击如图3-14所示的按钮,可以看到对话框关闭了。
图3-14 无JavaScript关闭对话框的交互示意
此效果实现的关键是在<form>元素上设置了method="dialog",这是个表单新特性,当设置了此声明后,在提交表单元素的时候,不再刷新页面或打开新窗口,而是关闭当前的<dialog>对话框元素。
而点击按钮触发表单提交的只能是'submit'性质的按钮,因此,万万不可将“我知道了”按钮设置为type="button"或者type="reset"。另外,还可以在'submit'性质的按钮上设置formmethod="dialog"实现同样的对话框关闭效果,例如:
2.关闭来源的判断
<dialog>元素的DOM对象上支持一个名为returnValue的属性,可以自动或手动设置对话框关闭行为的来源。
例如下面这个例子:
这段代码模拟Confirm类型的对话框,点击“确定”按钮会执行一段业务逻辑,然后关闭对话框,点击“取消”按钮则直接关闭对话框。无论是哪个按钮,最终都有关闭对话框这一行为,如果希望区分是哪个按钮触发的关闭,就可以使用这个returnValue属性。
其中有两种给returnValue属性传值的方法:
· 如果点击按钮触发了对话框关闭,则可以在按钮上设置value属性,例如value="cancel";
· 如果使用JavaScript代码触发了对话框关闭,则可以通过给close()方法传参的方式达到我们希望的效果,例如:
此时,我们通过访问dialog.returnValue就可以知道哪个按钮触发了对话框关闭。
眼见为实,你可以通过在浏览器中输入地址https://www.htmlapi.cn/3/2-2.html或扫码访问来体验。
例如,点击“取消”按钮后,页面上就会提示returnValue的值是'cancel',效果如图3-15所示。
图3-15 returnValue属性值示意
如果是通过其他方式关闭的,例如按下ESC快捷键,则returnValue的值会是空字符串。
3.自动聚焦特性
使用原生HTML元素作为前端组件的好处之一就是无障碍访问天然支持,无需额外的自定义。
比如这里的自动聚焦特性。
所谓“自动聚焦”,指的是对话框显示后,浏览器的焦点会自动聚焦到对话框内。
这种特性不仅有助于增强用户体验,还保证了键盘的无障碍访问能力。
还以上面出现过的那段HTML代码为例:
此时,如果我们使用dialog.show()方法让对话框显示,则其中的<input>输入框会自动获取焦点,无须用户再去点击聚焦,效果如图3-16所示。
图3-16 自动聚焦特性示意
类似地,如果是一个Alert提示对话框,则焦点会自动聚焦在“确定”按钮上,此时用户只需要按下回车键,对话框就会关闭,操作非常便捷,无须移动光标到“确定”按钮上,然后再点击。
需要注意的是,这种自动聚焦特性会自动聚焦第一个能够聚焦的元素,而不是智能识别是输入框还是按钮,例如下面这段内容:
此时,对话框打开的焦点则在<a>元素上,不过不要紧,要想聚焦到按钮上,按下Tab键往后索引一下就好了。
这是很多对话框组件所缺失的能力,即忽略键盘的无障碍访问。设想一下,如果对话框打开后,浏览器的焦点不在对话框之中,那么用户想要使用键盘访问对话框里面的内容就会很麻烦,不知道按下多少次的Tab键才能转入对话框元素中(对Tab键行为不清楚的读者,可以参阅第9.1节)。
这里还有一个细节需要注意,<dialog>元素的焦点聚焦特性只有在执行show()或showModal()方法的时候才会触发,如果是使用其他方法让对话框显示的,如以下代码所示,均无法自动聚焦到对话框中。
最后,如果对话框自动聚焦了,那么对话框关闭的时候,焦点会恢复到对话框显示之前的状态,不会影响正常的键盘访问。
4.showModal()方法与真正的对话框
前面几节虽然一直在说“对话框”,但是最终所呈现的效果并非真正意义上的对话框,而更像弹框,层级可控,位置可变,焦点可定义,更重要的是,<dialog>元素背后的元素内容是可以正常交互的。
真正的对话框应该是足够聚焦的(用户只能专注于当前的任务),而不是像一个普通的绝对定位浮层。
这里就不得不提一下<dialog>元素的“王炸”方法——showModal(),让<dialog>元素成为真正的对话框元素。
还是类似的HTML代码:
然后,我们使用showModal()方法让对话框显示,此时的效果如图3-17所示,可以明显看到多了个黑色半透明的模态层,同时除对话框元素以外的元素都无法交互了。
图3-17 showModal()效果与交互阻断
同时,相比show()方法,showModal()显示的对话框的定位方式也变了,不再是绝对定位,而是固定定位,且实现了真正意义上的居中效果。
上述几个明显区别参见表3-1。
表3-1 show()和showModal()显示的对话框的明显区别
你也可以通过在浏览器中输入地址https://www.htmlapi.cn/3/2-3.html或扫码访问来感受一下showModal()方法的样式表现。
模态对话框的半透明背景是可以使用CSS伪元素::backdrop自定义的(<video>视频的黑色背景也可以使用此伪元素进行样式自定义),例如下面这段CSS代码可以让默认的10%的黑色背景变成棋盘网格,效果如图3-18所示。
图3-18 ::backdrop伪元素自定义模态对话框背景
并且我们可以使用:modal伪类区分是普通对话框还是模态对话框。
当然,我对showModal()方法的评价如此之高,并不仅仅因为它多了个模态层,其还带来了现有对话框组件无法模拟的稀缺特性,且不止一个。
5.焦点隔离特性
前面提到show()方法自带聚焦特性,这个showModal()方法也有,除此之外,showModal()方法还有个焦点隔离特性。
即一旦模态对话框显示,则我们的Web页面的焦点只能出现在当前的对话框元素中,对话框之外的元素是不可能获取焦点的,哪怕使用JavaScript代码强制聚焦也不行。
也就是说整个网页中只有图3-19所示的三个元素可以获得焦点。
图3-19 焦点隔离特性示意
此特性极为稀缺,因为如果想要使用JavaScript模拟,仅存在理论上的可能性,例如需要使用HTML inert属性(详见第11.2节的介绍)让<dialog>以外的其他元素全部禁用,这实在太复杂,也不够优雅。
6.顶层特性
使用show()方法让对话框显示,对话框元素的层级是由CSS样式决定的,如果我们希望对话框元素的层级是最高的,则往往需要设置一个数值较大的z-index属性值。
而使用showModal()方法让对话框显示,对话框元素的层级自动最高,无需任何CSS样式设置,此特性被称为顶层特性,有个专门的名词描述此特性,叫作top-layer。
使用开发者工具查看<dialog>元素是可以看到此特性的,如图3-20所示。
图3-20 <dialog>元素的顶层特性
top-layer顶层特性实现的原理比较简单,直接在<html>窗体之外创建一个绘制图层(如图3-21所示),自然可以覆盖页面主体中的所有元素,哪怕页面中的元素的z-index值设置到无穷大也如此。
图3-21 top-layer在<html>窗体之外
但是有时一个页面中可能不止一个<dialog>元素,如果多个模态对话框同时显示,那么层级规则又是怎样的呢?
7.自动层级特性
如果一个页面中有多个对话框通过showModal()方法显示,那么后显示的对话框层级一定最高,这一点和传统的HTML元素不同。
传统的固定定位元素,如果不专门设置层级,则一定是位于DOM树后面的元素的层级最高,而模态对话框的层级高低与DOM位置无关,与呈现的时机有关。
比如下面这个例子,页面中有两个<dialog>元素,先显示第2个<dialog>元素,再显示第1个<dialog>元素。
你会发现,如果使用的是showModal()方法,则第1个<dialog>元素的层级在上面,如图3-22所示。
图3-22 showModal()方法的弹框1在上面显示
如果使用的是show()方法,则第1个<dialog>元素会在下面显示,如图3-23所示。
图3-23 show()方法的弹框1在下面显示
眼见为实,你可以通过在浏览器中输入地址https://www.htmlapi.cn/3/2-4.html或扫码访问来体验。
综合上述7点介绍,<dialog>元素显然不是普通的<div>元素可以模拟的,无论是从用户体验的角度还是从开发成本的角度看,只要想实现对话框效果,毫无疑问,一定优先使用<dialog>元素。