乐趣区

关于前端:浅谈-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 伪类选择器只会在聚焦由键盘导致时才失效,但可编辑元素如 <input type="text" /> 除外,这类元素不论聚焦是什么操作导致的都会失效,上面是一个简略的例子:

<style>
    /* 鼠标点击导致的聚焦,边框将是红色的 */
    /* Tab 按键导致的聚焦,边框将是绿色的 */
    :focus{outline:2px solid red;}
    :focus-visible{outline:2px solid green;}
</style>

<p>
    一般标签通过点击聚焦,边框是红色,<br />
    通过键盘的 Tab 键聚焦,边框是绿色。<br />
    <a href="javascript:void(0)">anchor</a>
    <button type="button">button</button>
    <span tabIndex="0">span[tabIndex="0"]</span>
</p>

<p>
    可输出的总会匹配 :focus-visible 选择器,<br />
    不论聚焦由什么操作导致,边框都是绿色。<br />
    <span contentEditable="true">span[contentEditable="true"]</span><br />
    <input type="text" placeholder="text" /><br />
    <textarea ></textarea><br />
</p>

↓ View & Code ↑

看着如同不错?然而 XJ 认为这个 :focus-visible 伪类选择器并没有设想中那么好用,首先是它对可输出的元素采取了非凡解决,这可能并不总是合乎咱们的需要,其次是兼容性问题,须要 Firefox85+ 和 Chrome86+ 能力反对,IE 和 Safari 你就别奢望了,更具体的兼容信息可查看 MDN 或 canIuse,如果咱们想要所有浏览器都反对,就得借助 JS 绑定事件来解决,而这则是下一章将会讲到的内容。

这里再补充一个信息,早在 Firefox4.0 的时代,Firefox 就有个差不多概念的伪类选择器既 :-moz-focusring,然而这个选择器最终并没有变成规范,并且它的性能和当初的 :focus-visible 伪类选择器有蛮大的差异,有些人可能会用这个选择器去做 Firefox 低版本的兼容写法,但实际上并没有多大的用途,这个选择器当初也曾经被废除,能够不必理睬了,所以在下面的 Demo 中 XJ 也没用到它。


应用 JS 来解决这个问题

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

<style>
    /* 先将 :focus 默认的所有的外边框都清理掉 */
    /* 当聚焦是因为按了键盘导致时显示红色边框 */
    :focus{outline:0;}
    .isKbd :focus{outline:2px solid red;}
</style>

<p>
    聚焦由点击操作触发时没有外边框,<br />
    聚焦由键盘触发时显示红色外边框。<br />
</p>
<p>
    <button type="button">button</button><br />
    <a href="javascript:void(0)">anchor</a><br />
    <input type="text" placeholder="text" /><br />
    <span contentEditable="true">span[contentEditable="true"]</span><br />
</p>

<script>
(function(){

// 获取 html 节点,创立用于判断操作状态的布尔值变量
var html = document.documentElement;
var isKbd = false;

// 当键盘被按下时,html 标签节点将被增加 isKbd 类名
html.addEventListener('keydown', function(){if(isKbd === false){html.classList.add('isKbd');
        isKbd = true;
    };
}, true);

// 当鼠标被按下时,html 标签节点将被移除 isKbd 类名
html.addEventListener('mousedown', function(){if(isKbd === true){html.classList.remove('isKbd');
        isKbd = false;
    };
}, true);

})();
</script>

↓ View & Code ↑

这样问题就解决了吗?答案是并没有!实际上不是只有点击和键盘操作会触发聚焦,应用 JS 操作以及浏览器的一些默认行为也有可能导致触发聚焦,咱们须要进一步的辨别判断,并且外边框设置也会存在一些非凡状况,除了可编辑的标签可能须要非凡看待,还有一些标签如 <svg> 中的 <a> 是不能设置外边框的,设置可能并不会失效,最初就是有些标签如 <audio> 是无奈监听键盘和鼠标事件的。

所以下面这个 Demo 也只是展现了一下大抵的思路,间隔真正的实用还有很大的一段距离,咱们须要分别出所有的可能导致聚焦的操作行为,并且还须要针对一些非凡标签进行区别对待,这实际上是一个比较复杂的问题,不是几行代码就能搞定的,作为一个一般的开发者,你未必有工夫和设施去钻研这种兼容问题,所以在这种状况下举荐应用现成的开源插件来解决,而这就是下一章咱们将要提到的内容了。


WICG: focus-visible 插件

业界有个 :focus-visible 伪类选择器的 polyfill 计划既 WICG – focus-visible,它的实现原理和下面那个 Demo 相似,只不过它是把类名增加到被聚焦的那个元素上,这样能够进行更加精准的管制,并且被增加的类名是 focus-visible 而不是 isKbd,其实这个计划和个别的 polyfill 还是有些差异,毕竟 CSS 伪类选择器是无奈模仿的,这个计划是用了类名来代替,上面是一个简略的 Demo:

<!-- 应用 CDN 引入 WICG - focus-visible 文件 -->
<script src="https://cdn.jsdelivr.net/npm/focus-visible@5.2.0/dist/focus-visible.min.js"></script>

<style>
/* 先将 :focus 默认的所有的外边框都清理掉 */
/* 当聚焦是因为按了键盘导致时显示红色边框 */
:focus{outline:0;}
.focus-visible:focus{outline:2px solid red;}
</style>

<p>
    聚焦由点击操作触发时没有外边框,<br />
    聚焦由键盘触发时显示红色外边框。<br />
    <button type="button">button</button><br />
    <a href="javascript:void(0)">anchor</a><br />
</p>

<p>
    可输出的标签聚焦总是会显示外边框,<br />
    不论聚焦是由点击导致还是键盘导致。<br />
    <input type="text" placeholder="text" /><br />
    <span contentEditable="true">span[contentEditable="true"]</span><br />
</p>

↓ View & Code ↑

这样问题就解决了吗?答案是并没有!这只是解决了聚焦行为的判断,之后还有款式的设置问题,首先是 outline 款式的局限性,这个款式并不能实现圆角(只有 Firefox 和最新版的 Chrome 能够),除非你的我的项目从头到尾都没用到圆角,否则 outline 在配合圆角标签显示时总会显得很难看,其次是 <svg> 中的 <a><map> 中的 <area> 不能设置 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 也是一个不错的抉择,上面是一个简略的例子:

<!-- 应用 CDN 引入 xj.focus 的 JS 和 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/xjZone/xj.focus@0.4.0/dist/xj.focus.min.css" />
<script src="https://cdn.jsdelivr.net/gh/xjZone/xj.focus@0.4.0/dist/xj.focus.min.js"></script>

<p>
    聚焦由点击操作触发时没有外边框,<br />
    聚焦由键盘触发时显示蓝色外边框。<br />
    <button type="button">button</button><br />
    <a href="javascript:void(0)">anchor</a><br />
</p>

<p>
    可输出的标签聚焦总是会显示外边框,<br />
    然而这个可在全局配置中进行批改的。<br />
    <input type="text" placeholder="text" /><br />
    <span contentEditable="true">span[contentEditable="true"]</span><br />
</p>

↓ View & Code ↑

xj.focus 插件默认提供了 outlinebox-shaodw 两种外边框款式,Safari 的表单控件因为不反对 box-shaodw,所以将主动应用 outline,但你也能够通过插件的全局配置进行自由选择,其次是插件对 <svg> 中的 <a><map> 中的 <area> 聚焦都采取了忽视解决,也就是不为它们设置外边框,让它们持续应用浏览器自带默认提供的外边框,同样的这个也可通过全局配置进行更改。

xj.focus 插件当然也不是完满的,因为 W3C 的一些规范的问题,所以它对 <audio><video> 的聚焦判断在局部浏览器中可能会不够精准(更多细节可参考文档),它提供的蓝色外边框可能也并不合乎你我的项目的需要(能够复制款式代码后调整色彩进行笼罩),但总的来说它也是一个足够业余的聚焦判断插件了,如果你不想本人解决聚焦的判断问题,也不想本人解决聚焦外边框的兼容,那用它就没错啦。


对于 focus 聚焦的一些知识点

XJ 在开发 xj.focus 插件时积攒下来的一些和聚焦相干的常识,如果你想进一步理解聚焦相干的内容,兴许能够成为不错的参考资料。

可聚焦的标签元素列表

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

标签,备注
<a> & <area>,svg 里的 a 在 Firefox 和 Safari 中无奈聚焦,并且在 IE10 中还不能设置聚焦外边框款式,设置不单不会失效,还会导致原有外边框款式失踪,能够思考通过 “svg a {}” 选择器将这类 a 筛选进去,map 里的 area 在 IE10 中也是不能设置聚焦 outline,并不会失效且会导致原有的外边框款式失踪。
<input>,不包含 type=”hidden” 的元素,因为它是 display:none;,并不会被显示进去,Safari 中 type 为 button, submit, reset, radio, checkbox, file, color, range, image 默认也是无奈聚焦。
<iframe>,兴许可被聚焦,但实际上 focus 事件并不会传递到父页面,:focus 伪类也不会失效。
<summary>,可聚焦的大前提是浏览器反对 details 和 summary 标签,IE18- 不反对则不能聚焦。
<audio>,audio 标签的 UI 按钮也是能够被聚焦的,然而那些按钮的款式无奈被设置。
<video>,video 标签的 UI 按钮也是能够被聚焦的,然而那些按钮的款式无奈被设置。
<object>,该标签在 IE10/11/18 中默认可聚焦,然而在其余浏览器中则不行。
<embed>,该标签在 IE10/11/18 中默认可聚焦,然而在其余浏览器中则不行。
<svg>,该标签在 IE10/11 中默认可聚焦,然而在其余浏览器中则不行。
<output>,该标签在 IE18 中默认可聚焦,然而在其余浏览器中则不行。
<button>,该标签在 Safari 不可聚焦,即便设置 tabIndex 也不行。
<select>,-
<textarea>,-
[tabIndex],[tabIndex]:not([tabIndex=”-1″])。
[contentEditable],[contentEditable]:not([contentEditable=”false”])。
CSS user-modify,在 CSS 中应用 moz-user-modify 或者 webkit-user-modify 款式让标签变得可编辑。
scrollableElement,可滚动的节点,在 Firefox 和较新的 Chrome 中都能够聚焦,然而 IE10/11/18 和 Safari 中都无奈聚焦。

辨别是否要增加外边框

当聚焦是 Tap 点击触发就不显示外边框,而当聚焦是通过键盘的 Tab 按键触发就显示出外边框,但实际上,状况并不总是这样单纯的。

#,形容
1,能够聚焦但不可输出的标签,如 <a><button> 和那些因为设置了 tabIndex 属性而变得可聚焦的一般标签,它们是最简略的,Tap 点击触发聚焦,就不显示外边框,通过键盘的 Tab 按键触发聚焦,就显示出外边框。
2,能够聚焦且能够输出的标签,如 <input> 标签和 <textarea> 标签,以及那些被设置了 contentEditable="true" 属性或者 user-modify 款式属性从而变得可编辑的标签,不论是由哪种操作模式触发了聚焦,都要显示出外边框。
3,能够聚焦但不能设置外边框的标签,如 <svg> 中的 <a><map> 中的 <area>,这类标签节点的外边框形态很可能是不规则的,所以浏览器也并不是通过简略的设置 CSS 的 outline 款式或 box-shaodw 款式来实现外边框,如果咱们贸然的为这类标签设置外边框款式,很可能不单设置不会失效,还会导致原有的外边框失踪,尤其是 Firefox 和 IE,一旦设置了,款式将会有效,外边框也会隐没,所以思考到兼容问题,最好是不要为这类标签节点设置聚焦款式,让浏览器持续保持原状最好。
4,能够聚焦但无奈判断聚焦形式的标签,如 <audio><video>,依据 W3C 的规范《media # user interface》,这两个标签的 ControlsUI 局部,点击和键盘等事件不会传递到 shadow-dom 顶层的根元素去,所以无奈通过绑定事件来判断聚焦是由哪种操作导致的,这问题比较复杂,首先是 IE 和 EDGE 始终都没遵循规范,所以反而能被监听到,Safari 到目前为止 (2022-11-16) 也是没遵循规范,这问题从 2014 年在 Webkit Bugzilla 被提起但至今都无人理睬,而 Firefox 与 Chrome 当初都曾经遵循规范,所以无奈监听事件,也就无奈判断聚焦是由什么操作导致的,xj.focus 插件最终的做法是,IE 和 Safari 继续执行惯例判断操作,Firefox84- 和 Chrome85- 将这两个标签的所有聚焦都当成是由 Tap 点击导致的,不显外边框,这会导致当应用 Tab 键聚焦时难以分别,而 Firefox85+ 和 Chrome86+ 曾经反对 :focus-visible 伪类选择器了,所以将会应用这个选择器辅助判断,就不会有问题,好在 Firefox 和 Chrome 更新换代比拟勤快,低版本的 Firefox 和 Chrome 沦亡得比拟快,状况会逐步恶化的。

focus 事件的触发办法

目前总共发现有以下 5 种办法能够触发 focus 事件,如果聚焦回调中 event 对象可被批改,那么 event.isTrusted 属性就是 false。

#,形容
1,通过鼠标或触屏的 Tap 点击。
2,通过按键盘的 Tab 按键触发,此外还有一种形式,对于 radio 按钮控件,应用键盘的方向键,既 可将焦点切换到同 name 属性值的其余 radio 按钮控件中,此时也会触发 focus 事件的。
3,调用 Node.prototype.focus() 办法,此时 event.isTrusted 属性仍旧是返回 true,因为事件尽管是由 JavaScript 引发的,然而因为事件对象并不能被批改,所以为 true,并且真的在 UI 界面上实现聚焦。
4,调用 EventTarget.prototype.dispatchEvent() 办法实现聚焦,这种触发模式的事件对象可批改,所以 event.isTrusted 属性会返回 false,由这个办法引发的 focus 事件,并不会真的在 UI 界面实现聚焦。
5,浏览器的自发行为,通常会在刷新页面或者唤醒某个页面标签后触发,这可能会随同着 document.onvisibilitychange 事件呈现,此时 event.isTrusted 属性仍然返回 true,和第三种触发模式其实很类似。

参考内容

Guilherme Simões – 绝不要移除 CSS 的 outline 外边框
Caitlin Geier – 设计有用的以及可用的焦点指示器的技巧
Steve Faulkner – 如何以可拜访的形式删除款式的 outline

WICG – Polyfill for :focus-visible
Lindsay Evans – Focus with Outline

W3C – Media – #user-interface

XJ.Chen – xj.focus

退出移动版