浅谈 :focus 伪类选择器和聚焦后 outline 边框的设置问题

浅谈 :focus 伪类选择器和聚焦后 outline 边框的设置问题

浏览器一般会自带 :focus{outline:1px solid blue;} 这样样式设置,当元素被聚焦时会显示出外边框,这样用户就能知道当前浏览器的焦点位置,但外边框的样式往往会显得多余又难看,所以开发人员时常会选择将外边框去掉,也就是设置 :focus{outline:0;}

但是将聚焦后的外边框去掉这种做法,实际上是不可取的,因为这将导致当用户使用键盘的 Tab 按键进行焦点定位时,根本不知道自己聚焦到什么地方去,这种情况下用户体验就会很差了,关于这一点,可以参考《绝不要删除样式的外边框》《为你的网站设置有用和可用的焦点指示器样式》这两篇文章(备注:两篇文章都是英文的)。

而我们所面临的问题,其实简单来讲就是,我们不希望在点击聚焦时出现外边框,因为样式很难看,但是又希望在使用键盘聚焦时显示出外边框,因为不这么做会影响到用户体验,前端领域一直有在探索这个问题要如何解决,在以前只能是使用 JS 进行聚焦的判断处理,而现在我们可以使用 CSS4 新增的 :focus-visible 伪类选择器来处理,接下来 XJ 会大致讲一下这两种方案的特点以及它们的局限性。


:focus-visible 选择器

:focus-visible 是 CSS4 新增的一个伪类选择器,它与 :focus 伪类选择器十分相似,都是在标签节点被聚焦的时候生效,唯一的区别是 :focus 伪类选择器不会区分导致聚焦的操作,只要聚焦了就会生效,而 :focus-visible 伪类选择器只会在聚焦由键盘导致时才生效,但可编辑元素如 除外,这类元素不管聚焦是什么操作导致的都会生效,下面是一个简单的例子:



普通标签通过点击聚焦,边框是红色,
通过键盘的 Tab 键聚焦,边框是绿色。
anchor span[tabIndex="0"]

可输入的总会匹配 :focus-visible 选择器,
不管聚焦由什么操作导致,边框都是绿色。
span[contentEditable="true"]


↓ View & Code ↑

看着好像不错?然而 XJ 认为这个 :focus-visible 伪类选择器并没有想象中那么好用,首先是它对可输入的元素采取了特殊处理,这可能并不总是符合我们的需求,其次是兼容性问题,需要 Firefox85+ 和 Chrome86+ 才能支持,IE 和 Safari 你就别奢望了,更详细的兼容信息可查看 MDNcanIuse,如果我们想要所有浏览器都支持,就得借助 JS 绑定事件来处理,而这则是下一章将会讲到的内容。

这里再补充一个信息,早在 Firefox4.0 的时代,Firefox 就有个差不多概念的伪类选择器既 :-moz-focusring,但是这个选择器最终并没有变成标准,并且它的功能和现在的 :focus-visible 伪类选择器有蛮大的差别,有些人可能会用这个选择器去做 Firefox 低版本的兼容写法,但实际上并没有多大的用处,这个选择器现在也已经被废弃,可以不用理会了,所以在上面的 Demo 中 XJ 也没用到它。


使用 JS 来解决这个问题

使用 JS 来解决这个问题以实现兼容,最简单的做法,就是绑定鼠标事件和键盘事件,在触发了键盘事件的时候就在 上添加一个类名如 isKbd,让 .isKbd :focus{} 样式规则生效,在触发了鼠标事件的时候就移除 上的 isKbd 类名让 .isKbd :focus{} 样式规则不生效,下面是一个简单的例子,当聚焦是点击导致的就没有外边框,当聚焦是按了键盘导致的就会有红色外边框:



聚焦由点击操作触发时没有外边框,
聚焦由键盘触发时显示红色外边框。


anchor

span[contentEditable="true"]

↓ View & Code ↑

这样问题就解决了吗?答案是并没有!实际上不是只有点击和键盘操作会触发聚焦,使用 JS 操作以及浏览器的一些默认行为也有可能导致触发聚焦,我们需要进一步的区分判断,并且外边框设置也会存在一些特殊情况,除了可编辑的标签可能需要特殊对待,还有一些标签如 中的 是不能设置外边框的,设置可能并不会生效,最后就是有些标签如 是无法监听键盘和鼠标事件的。

所以上面这个 Demo 也只是展示了一下大致的思路,距离真正的实用还有很大的一段距离,我们需要辨别出所有的可能导致聚焦的操作行为,并且还需要针对一些特殊标签进行区别对待,这实际上是一个比较复杂的问题,不是几行代码就能搞定的,作为一个普通的开发者,你未必有时间和设备去钻研这种兼容问题,所以在这种情况下推荐使用现成的开源插件来解决,而这就是下一章我们将要提到的内容了。


WICG: focus-visible 插件

业界有个 :focus-visible 伪类选择器的 polyfill 方案既 WICG - focus-visible,它的实现原理和上面那个 Demo 类似,只不过它是把类名添加到被聚焦的那个元素上,这样可以进行更加精准的控制,并且被添加的类名是 focus-visible 而不是 isKbd,其实这个方案和一般的 polyfill 还是有些差别,毕竟 CSS 伪类选择器是无法模拟的,这个方案是用了类名来代替,下面是一个简单的 Demo:






聚焦由点击操作触发时没有外边框,
聚焦由键盘触发时显示红色外边框。

anchor

可输入的标签聚焦总是会显示外边框,
不管聚焦是由点击导致还是键盘导致。

span[contentEditable="true"]

↓ View & Code ↑

这样问题就解决了吗?答案是并没有!这只是解决了聚焦行为的判断,之后还有样式的设置问题,首先是 outline 样式的局限性,这个样式并不能实现圆角(只有 Firefox 和最新版的 Chrome 可以),除非你的项目从头到尾都没用到圆角,否则 outline 在配合圆角标签显示时总会显得很难看,其次是 中的 中的 不能设置 outline 外边框,设置可能会无效。

我们可以改用 box-shadow 属性来做外边框以实现圆角,但 Safari 的表单控件并不支持这个属性,除非添加 appearance:none 的样式,但这样又会导致表单控件默认样式被破坏,而有些标签不能设置外边框,那是因为这些标签可能具有不规则的外轮廓,outlinebox-shaodw 无法处理这些不规则轮廓,所以设置可能会无效,那么多的问题太烦人了,有没有更现成的方案?有的,继续往下看。


用 xj.focus 插件来处理聚焦

XJ 自己编写了一个 xj.focus 插件,也可以当作是 :focus-visible 伪类选择器的 polyfill,但是跟 WICG 的 focus-visible 方案相比,xj.focus 插件提供了更多的 API 参数,允许你自行选择聚焦模式,并且它还自带了一个聚焦相关的 CSS 样式文件,用于解决样式的问题,如果你希望对聚焦能够有更多的细节控制或者懒得编写样式,那么 xj.focus 也是一个不错的选择,下面是一个简单的例子:





聚焦由点击操作触发时没有外边框,
聚焦由键盘触发时显示蓝色外边框。

anchor

可输入的标签聚焦总是会显示外边框,
但是这个可在全局配置中进行修改的。

span[contentEditable="true"]

↓ View & Code ↑

xj.focus 插件默认提供了 outlinebox-shaodw 两种外边框样式,Safari 的表单控件由于不支持 box-shaodw,所以将自动使用 outline,但你也可以通过插件的全局配置进行自由选择,其次是插件对 中的 中的 聚焦都采取了无视处理,也就是不为它们设置外边框,让它们继续使用浏览器自带默认提供的外边框,同样的这个也可通过全局配置进行更改。

xj.focus 插件当然也不是完美的,由于 W3C 的一些标准的问题,所以它对 的聚焦判断在部分浏览器中可能会不够精准(更多细节可参考文档),它提供的蓝色外边框可能也并不符合你项目的需求(可以复制样式代码后调整颜色进行覆盖),但总的来说它也是一个足够专业的聚焦判断插件了,如果你不想自己处理聚焦的判断问题,也不想自己处理聚焦外边框的兼容,那用它就没错啦。


关于 focus 聚焦的一些知识点

XJ 在开发 xj.focus 插件时积累下来的一些和聚焦相关的知识,如果你想进一步了解聚焦相关的内容,也许可以成为不错的参考资料。

可聚焦的标签元素列表

以下是目前发现可被聚焦的元素的列表,部分标签在不同浏览器中的表现并不一致,也许还有一些可聚焦的标签但没被发现的也不一定。

标签,备注
& ,svg 里的 a 在 Firefox 和 Safari 中无法聚焦,并且在 IE10 中还不能设置聚焦外边框样式,设置不单不会生效,还会导致原有外边框样式失踪,可以考虑通过 "svg a {}" 选择器将这类 a 筛选出来,map 里的 area 在 IE10 中也是不能设置聚焦 outline,并不会生效且会导致原有的外边框样式失踪。
,不包括 type="hidden" 的元素,因为它是 display:none;,并不会被显示出来,Safari 中 type 为 button, submit, reset, radio, checkbox, file, color, range, image 默认也是无法聚焦。