浅谈 :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
的款式,但这样又会导致表单控件默认款式被毁坏,而有些标签不能设置外边框,那是因为这些标签可能具备不规则的外轮廓,outline
和 box-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 插件默认提供了 outline
和 box-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