使用tabindex的正确姿势

发布于 大漠

首先要告诉大家的是tabindex并不是一个新属性(或者说新特性),但了解tabindex的真正作用或者说正确使用的人还是不多。最近在项目中就碰到一个问题,在部分安卓机上显式给div元素设置了tabindex='-8'这样的值,结果开启“旁白”的设备无法获得所需要的焦点。这到底是什么呢?我也一脸蒙逼,因为在我的理解范围中,tabindex的使用只有不显式设置值显式设置其值为0显式设置其值为负正整数或者显式设置其值为正整数。那么问题来了,如果要增强Web的可访问性,我们在实际的操作中应该怎么来设置(或者说正确使用)HTML的tabindex属性。今天和大家一起聊聊tabindex,如果感兴趣的话,请继续往下阅读。

tabindex是什么

从字面上来讲,tabindex就是一个组合词,其拆分出来就是tabindex,其中tab指的是键盘上的tab键(如果是Mac电脑的是➜|键),而index就相当于JavaScript数组中的每个项的索引号。

简单形象的描述即是:

tabindex是用户操作tab➜|)的顺序,同时在操作该键所获得的元素。

你可以先体验一下,打开任意一个Web页面或者Web应用程序(比如小站),然后一直按tab键,你可以看到今次获得焦点的元素,如下图所示:

如果从专业的角度来描述的话,tabindex是HTML的一个全局属性,所谓的全局属性是指:

HTML标签元素都可以用的属性,比如说idclass等属性都是HTML的全局属性。

而且tabindex属性是一个与键盘访问行为息息相关的属性,或者说是用户体验息息相关的属性。平常我们可能感觉不到它的价值,但是一旦你的鼠标坏了或者说只能通过键盘来操作;或者说面对其他的使用场景,比如说电视机、投影仪上访问Web页面或Web应用程序时(这种场景下只能使用遥控器),那么tabindex的作用就立杆见影了。另外就是喜欢使用快捷键的同学,tabindex的优势也能显现出来。

也就是说,tabindex属性是用于管理键盘焦点,决定元素是否能被选中,以及按下tab键过程中被选中的顺序,使用得当能够极大的提高Web页面或应用程序的易用性。如果应用不好(不恰当地使用时)会直接影响键盘用户对应用的可用性。要想更好的理解tabindex属性对应用的操作性是否有利还是不利,就很有必要了解一下焦点管理相关的知识。

Web中的焦点

既然tabindex是用来管理Web中元素的焦点,那么我们就有必要先了解一些关于焦点(聚焦)相关的知识点。

焦点是什么

对于依赖于焦点注意力的人来说,焦点决定了元素选中与否的区别,犹如决定白天和晚上的区别。如果是在Web中,焦点是指页面上交互元素之间发生的事情。这里所指的交互元素是指像<a>(链接元素)、button(按钮元素)和表单控件等元素。并且在Web页面或Web应用程序中,焦点决定在任何给定时刻页面中键盘事件的去向。例如,如果你在聚焦某休文本输入框(input)后开始键入内容,该输入字段将接收键盘事件并显示你键入的字符。它获得焦点期间,还会接收来自剪貼板的粘贴输入。

在Web页面或应用程序中,可获得焦点的元素有很多,详细的清单可以查看Focusable Elements,相关的测试用例可以查看What Elements Are Focusable?

在一个Web页面或Web应用程序中,在任何给定的时间都有一个元素具有焦点。当前聚焦项通用使用聚焦环(outline样式)标示出来,对于该样式取决于浏览器,也取决一起CSSer给可聚焦元素定义的样式。如下图所示:

刚才提到过,在Web页面或应用程序中,任何给定的时间都有一个元素可以获得焦点。如果页面刚加载,这个焦点可能是document,但是一旦你开始点击页面或按下键盘的tab键,这个焦点元素可能是前面提到的交互元素之一(也就是Focusable Elements的某一个)。除此之外,也可以通过document.activeElement找到当前获得焦点的元素。

另外,我们无法确定自己产品的用户群体都可以正常的操作鼠标(或者说鼠标属于正常可操作的情况)。换句话说,一些用户几乎是依赖于键盘或其他输入设备来操作计算机(和你的产品做有效的交互操作),甚至有些高级用户习惯了键盘的操作。对于这些用户群体而言,焦点就显得尤其重要,因为这是他们到达所有屏幕元素的主要途径(好比天黑回家,总是喜欢有路灯能照亮回的的路)。因此,Web AIM提供了一份检查清单,所有页面功能应该都能使用键盘来操作

什么是可聚焦的

Focusable Elements列出了Web页面或应用程序中可获得焦点的元素。表格清单中的元素都是属于具备内置的交互式HTML元素,也称得上是隐式可聚焦元素。同样意味着,它们是自动插入到tab键顺序中,并且内置了键盘事件处理,不需要开发者进行人为的干预。

言外之意,除了Focusable Elements所列清单元素之外的Web元素不属于不可聚焦元素。对于这些元素而言,用户操作tab键是无法获得焦点。因此用户也就无法和这些元素进行交互,特别是对于有障碍的人士。

焦点对谁有益

对于默认可获得焦点的元素在聚焦时样式上有别于不能聚焦的元素(一般是指:focus状态下outline默认样式),这样做的好处是可以帮助用户确定目前可交互的元素在页面上的位置。它们显示当前正在填充哪个表单字段,或者将要按下哪个按钮,如果是移动端上的话,告诉用户正在读哪个元素。

在PC端上,可能很多用户是使用鼠标,可以通过光标来指定所需要的元素(确定知道要操作元素位置),但并不是每个人都能用鼠标来操作(在移动端甚至是没有鼠标可操作)。

不能使用鼠标的用户并不仅仅是指有障碍性的用户(也就是大家所说的残疾人士),也有可能是你可能正在做别的事情,影响你正常操作鼠标(比如你正抱着婴儿在操作)

不过,受益的不只是键盘用户。对于那些注意力持续时间有限或有短期记忆问题的人,焦点提示光环(outline对应的样式)是有用的。

该不该删除元素聚焦样式

虽然聚焦样式对于部分用户群体是有益的,但这个样式效果并不是每个人都喜欢,有些人会觉得它们的样式(UI风格)很丑。也因为这个原因,很多开发者会选择删除元素聚焦样式,这样一刀切的做法对于键盘用户来说是有害的,这就是好比,开发者删除了鼠标光标对鼠标用户一样有害。

所以我们不应该像下面这样做,这样会删除浏览器的默认的outline(聚焦元素的光环效果):

:focus {
    outline: none; /* 请不要这样做,这是不友好的一种做法 */
}

即使用box-shadow来模拟一outline的光环效果,也最好将outline的颜色设置为transparent,因为box-shadow在高对比度模式下表现不好。

如果你想满足设计的需求,让聚焦元素光环效果能适合自己的主题风格,可以通过CSS来完成。不过对于不同元素的:focus可以设置不同的效果。比如ainputbutton等,可以在:focus下指定对应的样式。另外,我们还可以借助一些伪类选择器提供更适合场景的样式,比如:focus-visible:focus-within或者伪元素::before/::after

有关于聚焦元素的焦点光环更多的介绍还可以参阅下面这些教程:

源和焦点顺序

在介绍tabindex的时候提到过,其中index是指的Web中可聚焦元素的顺序。而这个顺序是和DOM结构的顺序是非常有关系的。

任何一个Web页面或应用程序都离不开最基本的结构,也就是常说的HTML,哪怕是现在用JavaScript框架构建的Web应用。HTML中的每个元素在源中都有先后顺序一说,谁在前,谁在后完全是依赖于源码编写时出现的时机。这些元素可以构建成一个树(DOM树):

在Web可访问性的设计中同样有类似于DOM树的特性,该特性基本上就是浏览器实际呈现给屏幕阅读器的内容。浏览器获取DOM树,并将其修改成适用于辅助技术的形式。我们将这个修改后的树称为无障碍树。类似下图这样:

上图来自于@BIBOSWAN ROY的《Accessibility Object Model》一文。对应的源码可以点击这里查阅

有关于无障碍树更多介绍可以阅读:

在构建Web页面或应用程序中的DOM元素有些是具备可交互性的,比如表单控件可滚动区域链接对话框(dialog、**选项卡(tab)**等。用户在使用键盘时可以对这些元素进行交互操作。比如在一个HTML页面中有一些文件控件和链接,并且当前显示一个模态对话框,该对话框本身有一个文本控件和一个按钮。

在这个场景中,可聚焦元素的层次结构将包括浏览器窗口,其中的子窗口包括HTML应用程序的浏览器选项卡。选项卡本身的子元素包括各种链接和文本控件以及模态框。模态框本身还包括文本控件和按钮。如下图所示:

上面是对于默认源而且是具有可聚焦特性元素的操作。而在CSS中,特别是FlexboxGridWeb布局中,开发者可以改变页面渲染时的顺序。那么人为的重置渲染顺序之后,可聚焦的顺序会有所差异吗?我们暂时把这个问题留到后面来和大家一起探讨。

在继续往下之前,这里再提一个问题。

既然tabindex可以用来管理焦点(可以给非聚焦元素设置聚焦行为),如果开发者显式的重置了焦点顺序会对焦点顺序有影响?

我们带着这两个问题继续往下阅读。

tabindex如何使用

我们花了很长的篇幅知道tabindex是什么以及怎么通过键盘的tab来和DOM元素进行交互。在源和源的顺序中我们清楚的知道任何一个Web页面或应用程序由原生元素组成,这些原生元素可以构成一颗DOM树,并且DOM位置提供的默认tab键顺序以供用户和页面进行交互。虽然这样做便于用户的交互,但有时需要修改tab键顺序,而在HTML中对元素进行物理移动(改变源的顺序)并不总是最优解方案,甚至缺乏可行性。在此类情况下,我们就需要tabindex属性来显式设置元素tab键位置。

tabindex的取值

tabindex的另一层意思是元素使用tab键进行聚焦(focus)时候的顺序值。言外之意,我们可以显式的给tabindex设置值,而且是数值(好比一个数组的索引值)。简单地说,tabindex接受一个0值、或正整数或负整数,它们都具有不同的差异。

tabindex取负值(<0

如果tabindex取值为负值,比如-1时,将会完全忽略焦点顺序中的元素。用户使用tab键的时候无法让元素获得焦点,但可以变成由代码获取焦点。也就是说,在按下tab键时,该元素不能获取到焦点,但是可以通过代码来获取到焦点。比如下面这个小示例:

在不同浏览器下其效果是不一样的:

注意在不同的浏览器下,操作tab键进行焦点切换是略有差异。

如果你在上面的示例操作了一波的话,你会发现即使你按了tab键,将会忽略tabindex="-1"的按钮(也就是第三个按钮)。另外在显式的给元素设置负值是,不管设置什么样的确切值实际上并不重要。因为任何负值都将从焦点中移除元素(按tab键不可以聚焦元素),所以值是-1还是-9999没任何差异。因此,为了可读性和一致性,最好都使用-1。也就是说,tabindex取值为负值时我们没有任何理由使用小于-1的任何负值。

虽然tabindex取值为负值时,趋于统一的值-1,但并不代表其它负值不起作用,作用是有用的,只是没有任何意义。这是因为,当一个元素设置tabindex的值为-1时,元素会变得 focusable,即 tab键无法获得元素焦点,但元素还是可以被鼠标或者JavaScript的focus获得

anchorElem.focus();

既然如此,就没有必要再使用小于-1的值。如果你用了,有些浏览器或哪一天说小于-1不合法时,那就有点悲慛了。正如文章开头提到的坑,就有可能是tabindex设置了小于-1的值。虽然我现在还无法验证是不是这个引起的坑,但填坑的确提将-8的值重置为-1的。 如果拿JavaScript方面的知识来对比的话,就好比JavaScript中字符串数值都有indexOf()方法,当无法匹配的时候返回的值就是-1tabindex的路数和indexOf()就有点相似。

tabindex取值为负值时,除了设置的值应该统一设置为-1之外,我们在给元素显式设置tabindex为负值时要小心。正如上面的示例,button按理说是一个可聚焦元素,但由于我们显式的给button元素设置了tabindex值为-1。只不过这样一来,原本可以通过tab键聚焦,但由于tabindex值为-1从整个页面的聚焦系列中移除(还能接收focus()事件来得到焦点)。也就是说,如果不希望让可聚焦元素失去tab键获得焦点的话,不应该显式的设置tabindex的值为-1;反之,如果希望让可聚焦元素失去tab键获得焦点,只需要显式的设置tabindex的值为-1

tabindex取值为负值还有一个好处。就是希望在不可聚焦的元素上能获得焦点,那么tabindex设置为-1就很有用。这样的示例也经常可见,比如Tabs组件、Modal组件等。就拿模态框来说吧,当模态框被人为触发打开时,焦点应该在模态框上,这样一来,屏幕阅读器将开始读取,键盘也能够在模态框中操作。由于模态框(可能是div元素)在默认情况下是不可聚焦的,所以在该元素上显式设置tabindex的值为-1就非常的有用(虽然键盘的tab键无法让其聚焦,但允许JavaScript的focus()获得焦点)。

如果你对具有可访问性的模态框的制作感兴趣的,可以阅读下面几篇文章:

tabindex取正值(>0

tabindex的还可以是取值大于0的设置,这是一个真正将元素放到聚焦顺序(序列中),但是它的位置将由特定的数字决定(大于0的正整数),即1开始,一直往上

将前面的示例做一些小调整,如下:

尝试着在不同的浏览器用tab键来操作上面的Demo。当你在第一个button上点击鼠标左键之后,然后按tab键,你会发现最新获得焦点的是tabindex显式设置了值为1的按钮。如下图所示:

如果你在DOM元素上显式的设置了tabindex的值大于0,就会将该元素跳至自然tab键顺序的最前面。如果有多个元素的tabindex都设置了大于0的值,那么tab键的顺序将以大于0的最小值为起点,从小到大排序

使用大于0tabindex被认为是一种反面模式,因为屏幕阅读器是按照源的DOM顺序来阅读,而不是按照tab键顺序来阅读。如果你需要一个元素更快地出现(屏幕阅读器最先捕获到的元素),那么应该将它移动到DOM中更前的位置。要实现这样的效果,就需要给元素显式的设置tabindex大于0(最先获取到元素焦点的最佳值是将tabindex设置为1)。

既然显式的给DOM元素设置tabindex大于0的值,特别是1的时候能最首先获得元素焦点(按tab键),那么它是不是真的是最佳方式呢?事实上并非如此。

显式的给DOM元素设置tabindex大于0是一个极坏的做法

作为开发人员,如果需要借助tabindex来让用户获得较好的焦点体验,这本就说明了页面本身的布局是存有问题。这样做只会让键盘用户(更重要的是依赖于辅助技术操作页面或应用程序的用户)感到困惑。

Web浏览器接受任何具大于0tabindex值,并将其提升到整个系列的前面,以便用户可以通过tab键来操作页面。只有当浏览器使用tabindex完成所有元素后,它才会返回到源顺序。无论你的tabindex是多大的值,它仍然会胜过所有没有显式设置tabindex的元素。

既然tabindex能设置大于0的任意值,不过这个任意值是有所限制的。MDN文档上有这么一句话

Note: The maximum value for tabIndex should not exceed 32767. If not specified, it takes the default value -1.

也就是说,大于0的值不能超过32767。如果超过该值,不同的浏览器会有不一样的表现:

  • 在IE浏览器下,如果tabindex值正好超出最大限制,也就是tabindex="32768"的时候,tabindex属性值无效被忽略;但是如果值继续往上超出,例如tabindex="32768"或者tabindex="123456",其行为表现和tabindex="-1"是一致的,可以被鼠标focus无法被键盘focus,真是非常怪异的表现。
  • 在Firefox和Chrome浏览器下,如果tabindex值正好超出最大限制,就跟完全没事似的,可以鼠标focus也能键盘focus。出现这种现象有两种可能:一是Firefox和Chrome没有32767的限制,二是有32767限制,但是浏览器按照最大值32767进行解析了。究竟是哪一种可能呢?经过我的测试,发现是第一种可能,Firefox和Chrome没有32767的限制,或者有限制,但是绝对比32767要大。

尽管在技术上tabindex设置大于0的值是有效的,但是使用大于0tabindex将会影响tab键在元素上的移动顺序。这种意想不到的行为可能会使某些元素看起来是无法通过键盘访问。这对于那些强依赖于键盘或辅助技术访问页面的用户而言是极为不友善的。除此之外,对于一些可访问性检测的工具而言也是痛苦的。比如Lighthouse检测到tabindex大于0时就会出现错误提示信息:

或者采用Firefox调试器中“无障碍环境”检测项,也将会检测到tabindex大于1会有警告信息:

键盘:避免使用大于0tabindex

如果你希望解除这样的警告,可以分情况来对待:

  • 对于可聚焦的元素,比如<a><button>或者表单控件等元素,tabindex可以不做任何显式的设置
  • 对于不可聚焦的元素,希望得到tab键操作能聚焦元素,可以显式的设置tabindex的值为0或者1;当tabindex的值设置为0时可能会改变用户通过tab键获取元素顺序;当tabindex的值设置为1时可能会打乱tab键获得焦点的顺序
  • 完全删除tabindex,并调整页面结构,以便用户按照最初希望的顺序对元素进行tab操作;即使你需要一个元素按照tab键顺序出现,那么应该考虑调整DOM结构,而不应该仅依赖于tabindex来操作
  • tabindex的值设置为-1,并通过JavaScript的focus()事件让用户在操作时能得到焦点

也就是说,不管选择哪种方式来解除无障碍检测的相关警告信息都应该确保最终的tab键顺序是符合用户的逻辑(即,符合用户与页面交互的行为)

这里再次强调:

tab键聚焦元素的顺序是由DOM中元素的顺序决定的,而不是依赖视觉上的布局来决定

简单地说,默认的tab键顺序 = DOM结构顺序 = 样式调整后的顺序。也就是说,CSS的一些样式会在渲染的页面上呈现出tab键顺序混乱的现象,但事实上它的顺序是按照DOM源结构顺序向用户展示的。样式上对tab键顺序有所影响的样式会和position相关属性(比如fixedrelativeabsolutesticky)、float、Flexbox布局中的order或CSS Grid中部分属性。因为这些属性会改变文档流(让元素脱离文档流)。详细的介绍稍后单独和大家阐述。

tabindex0

tabindex的取值中除了显式的设置-11(不建议设置小于-1和大于1的值,前面有阐述过)之外还可以设置为0。当tabindex取值为0时,元素的tab键顺序与其在源码中的顺序一致。默认情况下,如果元素本身是可获得焦点的就没有必要设置tabindex(除非你有特殊需求,改变它原有的默认顺序)。但在一些场景中,希望对非聚焦元素(比如divspan等)也能通过tab键获得焦点,而且让其在整个tab键的序列中,那么只需要显式的在非聚焦元素上设置tabindex的值为0即可。

比如下面这个示例:

可以尝试着用鼠标点击浏览器的地址栏,然后按键盘的tab键,在不同的浏览器下你将看到类似下面这样的效果:

何时使用tabindex

如果你足够仔细的话,你会发现在了解tabindex怎么使用的时候就会发现“tabindex属性对于强依赖于键盘的用户而言是非常有用的,但如果使用不当,结果是破坏性的”。也就是说,我们应该在不同的场景之下来显式的设置tabindex的值,甚至是不设置值。

何时使用tabindex小于0的值(tabindex < 0

正如前面所述,显式的在DOM元素上设置tabindex小于0的值(就算要设置小于0的值,也强烈建议设置值为-1),可以不让tab键获得焦点,但是可以通过JavaScript的focus()获得焦点。

如果要拿实例来阐述的话,我还是会拿模态框为例(因为模态框的示例的确是一个较好的示例)。在Web页面或应用程序中,开发者制作一个模态框可能喜欢使用非聚焦元素,比如divsection这样的标签元素。当用户在页面上和模态框进行交互的时候,即用户点击了一个按钮或一个链接或一个区域,模态框会打开,在这个时候我们希望焦点移到模态框上面来,这样做,屏幕阅读器就会读出它的内容。但是,我们不希望模态框容器自身能够接收tab。而对这样的场景,在模态框容器上显式设置tabindex的值是-1,是一个较好的方式。

比如Bootstrap Framework中的Modal组件

如果熟悉其JS源码的话,你会发现在源码中通过focus()事件做了焦点处理:

// 源码省略了上下文
// ...
EventHandler.one(target, Event.SHOW, showEvent => {
    if (showEvent.defaultPrevented) {
        return
    }

    EventHandler.one(target, Event.HIDDEN, () => {
        if (isVisible(this)) {
            this.focus()
        }
    })
})
// ...

上面的代码省略了源码上下文,不易于理解。我们把整个模态框示例简化一下,可以类似下面这样来操作:

<div id="modal" tabindex="-1">
    <!-- 模态框内容 -->
</div>


function openModal() {
    document.getElementById("modal").focus();
    
    // 控制模态框的其他逻辑代码
}

更系统的介绍,可以查阅前面提供的有关于模态框制作的一些教程。

何时使用tabindex等于0

在介绍tabindex取值为0时我们可以得知,该方式的设置可以让非聚焦元素类似于聚焦元素一样,按tab键或focus()可以得到焦点,不同的是在tab键的序列中按DOM源的顺序排列。

随着JavaScript框框的流行或者采用Web Component开发Web页面或Web应用的时候,会用一些自定义元素,比如自定义的表单控件(最常见的selectcheckboxradio等)、按钮等。而这些元素它在默认情况下是不可聚焦的,因为它实际上并不是一个可聚焦元素。我们可以使用tabindex设置为0这种方式给这些元素添加可聚焦的功能,并像任何常规的可聚焦元素那样指定它的焦点顺序。

比如下面这样的一个自定义按钮组件:

<my-custom-button tabindex="0" role="button">Click me!</my-custom-button>

或者下面这样的一个自定义checkbox

何时使用tabindex大于0的值(tabindex>0

使用大于0tabindex被认为是一种反面模式,显式的给DOM元素设置tabindex大于0的值,特别是1的时候能最首先获得元素焦点(按tab键),会给用户造成一定的误解。也就是说,在使用tabindex时几乎没有任何理由使用大0的值。如果你在业务中发现要通过tabindex取大于0的值来改变元素可聚焦的顺序,那么说明你的DOM结构已存有一定的问题,这个时候应该做的是更改HTML元素的源顺序。

简单小结一下

tabindex设置的好对于改善用户体验是有益的,特别是对于那些过渡依赖于键盘操作的用户群体更是如此。但世间万物总是两极的,用的好就有益处,用的不好反而会有坏处。前面我们花了较长的篇幅和大家一起学习了tabindex的取值以及如何、何时用。这里再次简单的小结一下,就当作是tabindex在实际应用的最佳实践(也可以当作使用规则):

  • tabindex="-1":设置了tabindex-1的元素,该元素不会被置于页面的tab键序列中,但可以通过JavaScript的focus()获取,允许脚本设置元素的焦点。当你需要将焦点转移到通过脚本或用户操作之外更新的内容时,该设置非常方便
  • tabindex="0":可以让非常聚焦元素按自然顺序出现在tab键序列中
  • tabindex="1":不要设置tabindex="1"或任何大于0的值

不二法则,能用默认获得焦点的元素尽量使用

聚焦和tabindex在浏览器中的表现行为

由于测试资源有限,下面测试的结果未必完全准备,在整理本文所基于的环境是:操作系统macOS Mojave 10.14.5Chrome 78Firefox 70Safari 12.1.1

在HTML元素集中,部分元素本身就是可聚焦元素(比如buttona和表单控件),部分元素就是非聚焦元素(比如divspan等)。不管是可聚焦或者非聚焦元素,都可以显式设置tabindex,这是因为tabindex是一个全局属性。只不过不同的tabindex值会带来不一样的表现行为,特别是在不同的浏览器(或终端)下。接下来我们将以下面这个示例为基础,来看看可聚焦元素和非可聚焦元素在设置不同值的表现结果。

<button class="button" type="button">我是button元素(未设置tabindex)</button>
<div class="button" role="button">我是div元素(未设置tabindex)</div>
<button class="button" type="button" tabindex="-1">我是button元素(tabindex="-1")</button>
<div class="button" role="button" tabindex="-1">我是div元素(tabindex="-1")</div>
<button class="button" type="button" tabindex="0">我是button元素(tabindex="0")</button>
<div class="button" role="button" tabindex="0">我是div元素(tabindex="0")</div>
<button class="button" type="button" tabindex="">我是button元素(tabindex="0")</button>
<div class="button" role="button" tabindex="">我是div元素(tabindex="")</div>

加上一点样式,效果看上去如下:

注意,示例中没有设置tabindex大于0的值,因为我们在实际使用时应该尽量避免或者不设置tabindex大于0的值。

可以将上面的Demo在不同的浏览器中打开,在按tab进行切换之间先用鼠标点击body区域。测试出来的结果如下:

  Chrome Firefox Safari 备注
1(button) 未显式设置tabindex
2(div) 未显式设置tabindex
3(button) 显式设置tabindex="-1"
4(div) 显式设置tabindex="-1"
5(button) 显式设置tabindex="0"
6(div) 显式设置tabindex="0"
7(button) 显式设置tabindex="",但未赋值
8(div) 显式设置tabindex="",但未赋值

你可能看到的效果类似下面这个录屏:

虽然规范是上是这样定义的,但是到真正的客户端的表现还是有所差异的。从测试的结果来看:

  • 显式设置tabindex="0"的元素(不管是可聚焦元素还是非聚焦元素)都可能得到焦点
  • 显式设置了tabindex="-1"的元素都无法得到焦点
  • 除了Safari浏览器,只要是可聚焦的元素都可以正常得到焦点
  • 非可聚焦元素,显式设置了tabindex值,但只未赋予任何值的情况之下都无法正常获得焦点

从这个测试结果上来看,大部分都是符合我们预期的。上面我们的操作是通过tab键来测试的,不过从前面了解的知识点来看,显式设置了tabindex="-1"的元素虽然无法通过tab键获得焦点,但可以通过JavaScript的focus()事件获得焦点。

CSS样式会给tab序列造成混乱

前面提到过,CSS中的一些样式渲染会让用户操作tab造成一定的混乱。特别是CSS Flexbox和Grid,我们这里拿Flexbox来举例,Flexbox中的order属性会改变页面上排列。

比如下面这样的一个示例:

<button type="button">1</button>
<button type="button">2</button>
<button type="button">3</button>
<button type="button">4</button>

我们分两个排列,这样易于对比(一个是默认的按照DOM源顺序排列,另一个通过order改变排列顺序),效果如下:

尝试着在浏览器中用tab切换查看相应的效果:

正如你所看到的结果,不同的浏览器也存在一定的差异。到目前仅从CSS还没有较好的解决方案,需要借助JS来做处理。这个实例只是想告诉大家,部分CSS属性(改变文档流的CSS属性)会在渲染后页面效果上看上去tab键的混乱,但事实他还是依赖于DOM源的顺序进行切换。

有关于这方面详细的介绍可以阅读:

自动获得焦点

很多时候,我们希望在页面加载的时候就能指定某个元素自动获得焦点。在HTML中,我们可以显式的给元素设置autofocus属性。该属性是一个布尔属性,规定当前页面加载时是否自动获得焦点。比如我们在一个可聚焦的元素上,只要显式设置了autofocus即可自动获得焦点:

<button type="button" autofocus>Click Me</button>

当有了autofocus属性,inputteextareabutton元素都能在页面加载中被选中。例如Google首页,访问该页面的99%人员都用它来进行搜索,所以页面一旦加载,光标必然定位在input里。另外,对于非可聚焦元素,autofocus属性并不好用。

焦点事件

一个元素成为active元素就等于获得了焦点,会触发这个元素的focus事件。有元素获得焦点,一般就有元素会失去焦点,失去焦点的元素触发blur事件。这两个事件几乎是同时发生的,但他们的执行顺序有区别,blur事件的监听方法先执行,然后才是focus事件的监听方法。 元素在document没有加载完之前是不能获得焦点的,获得焦点的元素通过documnt.activeElement可以访问。

小结

我们花了很长的一个篇幅和大家探讨论tabindex的使用。如果你希望自己的产品能给用户提供一个更好的体验,特别是那些强依赖于键盘操作的用户。那么就有必要正确的使用tabindex,因为tabindex设置的对能用户更好的体验,反之,用的不好就会造成更为混乱的局面,达到相反的效果。要正确或者合理的用好tabindex就非常有必要花些时间阅读前面的内容。我想对你会有所帮助的。