交互元素的「:focus-visible」

856 阅读4分钟
原文链接: zhuanlan.zhihu.com

我们开发基于 Vue.js 的百度商业系统的组件库 VEUI 已经快一年了(速度的确有点慢 😂),期间也遇到一些问题,后面可能不定期地发一些文章来说说。

今天讲的是标题中的:focus-visible。这是个什么东西呢?:focus-visible之前也叫:focus-ring,不久前变更了话术。它是 CSS Selectors Level 4 草案中的一个项目。也是一个正在 WICG 孵化的项目。我们不直接看规范,先来看个例子:


这是 Ant Design 按钮组件的一个例子。我在这里遇到的问题是,当点击其中一个按钮后,该按钮将匹配 :focus 状态,那么组件 :focus 对应的样式就会展现。可是在一个按钮组中,会让人有一种「按钮被选中」的错觉。这里用 Ant Design 举例子,并不是因为他们没有处理好细节,而是因为只有注重细节的项目才会去设计、实现 :focus 样式。只有此时,才会出现这样的细节问题。相比之下,有很多组件库完全没有考虑键盘访问,所以也就不会遇到此类问题。

但我们发现,Chrome 中的原生按钮却不会在点击后显示可视的聚焦样式,仅在使用键盘导航时才会显示:

然而,当我们为 <button> 添加定制的 :focus 样式后,这个逻辑就不复存在了,会和之前 Ant Design 按钮的状态一致,不论是点击还是键盘导航,都会展现可视的聚焦样式。也就是说,Chrome 内部记录了每次聚焦来源,在点击时是不会激活默认的视觉样式的,但开发者目前无法利用这个信息。于是大家就提出,能不能提供一个新的伪类,让开发者可以自行控制这个状态的样式?我没有考证,但可以猜测这可能是 :focus-ring 的来源:匹配类似 Chrome 在 <button> 上加「环」的逻辑。但是这个名字和视觉样式耦合了,于是后来被重命名为了 :focus-visible,即「需要展现视觉样式的聚焦状态」。

:focus-visible 的规范并没有强行约束匹配逻辑,而是给了 UA 很大的空间。目前 WICG/focus-visible 提供了 polyfill(严格来说不算 polyfill,因为开发者需要写 .focus-visible 而非 :focus-visible ),当前逻辑为,仅键盘导航触发的聚焦状态会为目标元素添加 focus-visible 类。而 Firefox/Safari 下,这个问题从源头上就不存在,因为所有点击都不会使目标元素获取焦点,点击后 document.activeElement 将会是 <body>。这其中的问题也很明显——如果同时使用鼠标键盘,点一下按钮后按 tab 键,焦点就会跑到页面第一个可聚焦的元素上去了,非常不符合预期。

仔细考虑一下,我们在什么时候需要对焦点做视觉提示?我认为是这样的:

  1. 对于键盘这种「线性」的导航,因为在按下 tab 键前,我们可能并不清楚下一个聚焦元素会是哪个,所以视觉上的提示是非常必要的;
  2. 对于鼠标(触摸板)等对屏幕进行「随机访问」的设备,只需直观地聚焦到被点击的元素,因为我们已知自己点击的是哪个元素;
  3. 对于通过程序调用元素的 focus() 方法进行聚焦时,基本可以认为被聚焦的元素和之前聚焦的元素并不一致,此时视觉上的提示也是有必要的。

所以,我个人认为,只需让「非随机访问」时获取的焦点元素匹配 :focus-visible,就能满足大多数情况的需求了。目前 polyfill 代码主要由一位 Google 的工程师在维护,在 WICG/focus-visible#88 中我提出了这种策略,一番讨论以后对方已经迅速根据新的策略大幅调整了实现,新 commit 目测可用,正在等待大家的进一步 review。

VEUI 的默认样式包 veui-theme-one 目前也依赖了这个 :focus-visible 的「polyfill」,希望能在各种用户的使用时提供最完美的交互体验。


VEUI 目前还在 alpha 内测,未正式发布。后面待文档补充完善后再正式发布,希望能够得到大家的认可与支持。