CSS `focus-visible` / `focus-within` 与 `keyboard-only` 导航体验优化

各位观众,各位朋友,大家好!我是今天的主讲人,江湖人称“代码老中医”,专治各种疑难杂症,尤其是前端方面的。今天咱们聊聊CSS里两个挺有意思的家伙:focus-visiblefocus-within,以及它们如何拯救我们可怜的keyboard-only导航体验。

开场白:键盘侠的呐喊

先问大家一个问题:你们有多少人是键盘党?注意,我说的是纯键盘操作,鼠标能不用就不用的那种。如果你是,那么恭喜你,你一定经历过这样的痛:

  • 焦点乱飞:Tab键按得飞起,但根本不知道现在焦点在哪儿!
  • 样式丑陋:默认的focus样式丑的一批,还跟页面风格格格不入!
  • 误判敌友:鼠标点一下也触发focus样式,影响美观,用户体验极差!

这些问题,直接导致了键盘侠们寸步难行,严重影响了用户体验。别急,今天咱们就来拯救他们,啊不,是拯救我们的网站!

第一节课:focus的那些事儿

在深入focus-visiblefocus-within之前,我们得先搞清楚focus是个什么东西。简单来说,focus就是“焦点”。当一个元素获得焦点时,就意味着用户可以通过键盘与它进行交互(比如输入文字、点击按钮等等)。

HTML元素默认情况下,有些是可以获得焦点的,比如<a><button><input><select>等等。有些元素默认不能获得焦点,比如<div><span><p>等等。但是,我们可以通过tabindex属性让任何元素获得焦点。

  • tabindex="0":表示元素可以获得焦点,并且按照HTML结构顺序获得焦点。
  • tabindex="-1":表示元素可以获得焦点,但是不能通过Tab键获得焦点,只能通过JavaScript来设置焦点。
  • tabindex="任意正整数":表示元素可以获得焦点,并且按照数字大小顺序获得焦点(数字越小,优先级越高)。不建议使用,容易造成混乱。

代码示例:给一个div加上焦点

<div tabindex="0">
  我是一个可以获得焦点的 div
</div>

默认focus样式的弊端

默认情况下,浏览器会给获得焦点的元素添加一个默认的outline样式。这个样式通常是一个蓝色的边框,在不同的浏览器中可能略有差异。

这个默认的样式,有两个问题:

  1. 丑! 真的,大部分情况下它跟你的网站风格格格不入。
  2. 鼠标党也触发! 用户用鼠标点击元素时,也会触发focus样式,这可能会让他们感到困惑,因为他们并没有使用键盘进行导航。

第二节课:focus-visible:键盘侠的专属福利

focus-visible的出现,就是为了解决上述问题的。它是一个CSS伪类选择器,只有在键盘导航时,元素获得焦点才会应用样式。

重点: 只有键盘导航才会触发!鼠标点击不会触发!

使用方法:

/* 默认样式 */
button {
  border: 1px solid transparent; /* 隐藏默认边框 */
}

/* 键盘导航时,按钮获得焦点时的样式 */
button:focus-visible {
  border: 1px solid blue; /* 显示蓝色边框 */
  outline: none; /* 移除默认 outline */
}

代码解释:

  • button { border: 1px solid transparent; }: 这行代码是为了隐藏按钮默认的边框。有些按钮默认会有边框,为了更好地控制焦点样式,我们先把它隐藏掉。
  • button:focus-visible { ... }: 这行代码是focus-visible的核心。只有当用户使用键盘导航,并且按钮获得焦点时,才会应用这里的样式。
  • outline: none;: 移除浏览器默认的outline样式,避免与自定义的焦点样式冲突。

兼容性问题:

focus-visible的兼容性并不是很好,老版本的浏览器可能不支持。为了解决这个问题,我们可以使用polyfill。

polyfill: focus-visible.js

这是一个JavaScript库,可以为不支持focus-visible的浏览器提供支持。

使用方法:

  1. 下载focus-visible.js: 可以从npm安装 npm install focus-visible或者直接从github下载。
  2. 在你的HTML文件中引入focus-visible.js

    <script src="focus-visible.js"></script>
  3. 然后就可以像上面一样使用focus-visible了。

更优雅的写法: :focus-visible + :not(:focus-visible)

有时候,我们需要对鼠标点击和键盘导航分别设置不同的样式。这时候,可以结合:not(:focus-visible)来实现。

/* 默认样式(鼠标点击或其他方式获得焦点) */
button:focus {
  /*  */
  outline:none;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

/* 键盘导航时,按钮获得焦点时的样式 */
button:focus-visible {
  outline: 2px solid blue;
  box-shadow: none;
}

/* 鼠标操作时候移除 focus-visible 的样式 */
button:focus:not(:focus-visible) {
    outline:none;
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

代码解释:

  • button:focus { ... }: 这行代码定义了按钮获得焦点时的默认样式,包括鼠标点击或其他方式。
  • button:focus-visible { ... }: 这行代码定义了键盘导航时,按钮获得焦点时的样式。
  • button:focus:not(:focus-visible){...} 这行代码定义了当元素获得焦点,但不是通过键盘导航的时候,移除focus-visible的样式,恢复默认的样式,这样可以避免鼠标点击的时候,应用了键盘导航的样式,造成视觉上的混乱。

第三节课:focus-within:父元素的守护者

focus-within是另一个CSS伪类选择器。当元素自身或者它的任何后代元素获得焦点时,该元素就会应用focus-within样式。

重点: 只要后代元素获得焦点,父元素也会应用样式!

使用场景:

  • 表单验证:当表单中的任何一个输入框获得焦点时,可以高亮显示整个表单。
  • 导航菜单:当导航菜单中的任何一个链接获得焦点时,可以高亮显示整个菜单。
  • 卡片组件:当卡片中的任何一个元素获得焦点时,可以高亮显示整个卡片。

代码示例:高亮显示表单

<form class="form">
  <label for="name">姓名:</label>
  <input type="text" id="name" name="name"><br><br>
  <label for="email">邮箱:</label>
  <input type="email" id="email" name="email"><br><br>
  <button type="submit">提交</button>
</form>
.form {
  border: 1px solid #ccc;
  padding: 20px;
}

.form:focus-within {
  border: 2px solid blue;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

代码解释:

  • .form { ... }: 定义了表单的默认样式。
  • .form:focus-within { ... }: 当表单或者表单内的任何一个元素获得焦点时,表单的边框会变成蓝色,并且添加阴影。

结合focus-visiblefocus-within

我们可以将focus-visiblefocus-within结合起来使用,实现更精细的控制。

代码示例:只在键盘导航时高亮显示表单

.form {
  border: 1px solid #ccc;
  padding: 20px;
}

.form:focus-within:focus-visible {
  border: 2px solid blue;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

代码解释:

  • 只有当表单或者表单内的任何一个元素通过键盘导航获得焦点时,表单才会高亮显示。

第四节课:提升keyboard-only导航体验的最佳实践

除了使用focus-visiblefocus-within之外,还有一些其他的最佳实践可以帮助我们提升keyboard-only导航体验。

  1. 合理的tabindex顺序

    确保tabindex的顺序与HTML结构顺序一致,避免焦点在页面上乱跳。尽量避免使用tabindex="任意正整数",容易造成混乱。

  2. 清晰的焦点指示器

    使用清晰、醒目的焦点指示器,让用户能够清楚地知道当前焦点在哪里。可以使用outlineborderbox-shadow等属性来创建焦点指示器。

  3. 避免焦点陷阱

    确保用户可以通过键盘将焦点移动到页面上的每一个可交互元素,并且能够顺利地离开这些元素。避免出现焦点被困在某个区域无法移动的情况。可以使用JavaScript来监听键盘事件,并在必要时手动移动焦点。

  4. 语义化的HTML

    使用语义化的HTML标签,比如<button><input><a>等等。这些标签默认就具有可访问性,可以减少我们的工作量。

  5. 使用ARIA属性

    ARIA(Accessible Rich Internet Applications)是一组用于增强Web内容可访问性的属性。可以使用ARIA属性来描述元素的角色、状态和属性,帮助屏幕阅读器更好地理解页面内容。

表格总结:focus-visible vs focus-within

特性 focus-visible focus-within
触发条件 元素通过键盘导航获得焦点 元素自身或其后代元素获得焦点
应用对象 获得焦点的元素 元素自身
主要用途 区分键盘导航和鼠标操作,提供不同的焦点样式 高亮显示包含焦点的容器,用于表单、菜单等组件
兼容性 较差,需要polyfill 良好

结束语:人人为我,我为人人

提升keyboard-only导航体验,不仅仅是为了那些使用键盘导航的用户,也是为了我们自己的网站。一个可访问性良好的网站,能够吸引更多的用户,提升用户满意度,最终带来商业价值。

记住,Web开发不仅仅是让网站看起来漂亮,更重要的是让每个人都能够轻松地使用它。让我们一起努力,打造一个更加美好的互联网世界!

好了,今天的讲座就到这里。希望大家有所收获,下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注