构建悬浮操作按钮 (FAB) 组件

简要介绍如何构建颜色自适应、响应迅速且可访问的 FAB 组件。

在这篇博文中,我想分享我对如何构建颜色自适应、响应迅速且可访问的 FAB 组件的看法。试用演示版查看源代码

如果你更喜欢视频,可以参考本博文的 YouTube 版本:

概览

FAB 在移动设备上比桌面设备更常见,但在这两种场景中都很常见。它们始终显示主要操作,方便且无处不在。这种用户体验风格因 Material 界面而出名,如需查看它们的使用和放置位置建议,请点击此处

元素和样式

这些控件的 HTML 涉及一个容器元素以及一组(一个或多个)按钮。容器会将 FAB 放置在视口内并管理按钮之间的间隙。这些按钮可以是迷你按钮,也可以是默认按钮,主要操作和次要操作之间可以有很大差异。

FAB 容器

此元素可以是常规的 <div>,但对于视力不佳的用户,我们不妨使用一些有用的属性来标记该元素,以说明此容器的用途和内容。

FAB 标记

先从 .fabs 类入手,让 CSS 接入 CSS 以设定样式,然后添加 role="group"aria-label,这样它不仅仅是一个通用容器,而且具有命名和目的性。

<div class="fabs" role="group" aria-label="Floating action buttons">
  <!-- buttons will go here -->
</div>

FAB 样式

为使用方便,FAB 始终固定在视口内。这是位置 fixed 的一个很好的用例。在此视口位置中,我选择了使用 inset-blockinset-inline,以便位置与用户的文档模式相称,例如从右到左或从左到右。自定义属性还用于防止重复,并确保与视口的底部边缘和侧边缘的距离相等:

.fabs {
  --_viewport-margin: 2.5vmin;

  position: fixed;
  z-index: var(--layer-1);

  inset-block: auto var(--_viewport-margin);
  inset-inline: auto var(--_viewport-margin);
}

接下来,为容器提供 flex 属性,并将其布局方向更改为 column-reverse。这会将子项堆叠在一起(列),并反转其视觉顺序。这样做的效果是使第一个可聚焦元素成为底部元素,而不是顶部元素;根据 HTML 文档,焦点通常位于顶部。颠倒视觉顺序可为视力正常的用户和键盘用户提供更好的体验,因为主要操作的样式大于迷你按钮,向视力正常的用户表明该操作是主要操作,键盘用户将焦点设为源中的第一项。

显示了两个 FAB 按钮,并且开发者工具叠加在它们的网格布局上。通过条纹图案显示它们之间的间距,同时显示计算出的高度和宽度。

.fabs {
  …

  display: flex;
  flex-direction: column-reverse;
  place-items: center;
  gap: var(--_viewport-margin);
}

居中是通过 place-items 处理的,而 gap 会在容器中的所有 FAB 按钮之间增加空间。

悬浮操作按钮按钮

现在,我们来设置一些按钮的样式,使其看起来像悬浮在所有内容之上。

默认悬浮操作按钮

第一个要设置样式的按钮是默认按钮。这将作为所有 FAB 按钮的基础。稍后,我们将创建一个变体来实现替代外观,同时尽可能减少修改这些基本样式。

FAB 标记

<button> 元素就是合适的选择。首先我们将它作为基础,因为它提供出色的鼠标、触摸和键盘用户体验。这种标记最重要的方面是使用 aria-hidden="true" 向屏幕阅读器用户隐藏图标,并向 <button> 标记本身添加必要的标签文本。在这些情况下添加标签时,我还喜欢添加 title,以便鼠标用户可以了解图标希望传达的信息。

<button data-icon="plus" class="fab" title="Add new action" aria-label="Add new action">
  <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">...</svg>
</button>

悬浮操作按钮样式

首先,我们将按钮转换为带有强烈阴影且带有内边距的圆形按钮,因为这些是按钮的第一个定义功能:

.fab {
  --_size: 2rem;

  padding: calc(var(--_size) / 2);
  border-radius: var(--radius-round);
  aspect-ratio: 1;
  box-shadow: var(--shadow-4);
}

接下来,我们来添加颜色。我们将使用之前在 GUI 挑战中使用的策略。 创建一组明确命名的自定义属性(用于静态保存浅色和深色),然后创建一个自适应自定义属性,该属性将根据用户系统对颜色的偏好设置为浅色或深色变量:

.fab {
  …

  /* light button and button hover */
  --_light-bg: var(--pink-6);
  --_light-bg-hover: var(--pink-7);

  /* dark button and button hover */
  --_dark-bg: var(--pink-4);
  --_dark-bg-hover: var(--pink-3);

  /* adaptive variables set to light by default */
  --_bg: var(--_light-bg);

  /* static icon colors set to the adaptive foreground variable */
  --_light-fg: white;
  --_dark-fg: black;
  --_fg: var(--_light-fg);

  /* use the adaptive properties on some styles */
  background: var(--_bg);
  color: var(--_fg);

  &:is(:active, :hover, :focus-visible) {
    --_bg: var(--_light-bg-hover);

    @media (prefers-color-scheme: dark) {
      --_bg: var(--_dark-bg-hover);
    }
  }

  /* if users prefers dark, set adaptive props to dark */
  @media (prefers-color-scheme: dark) {
    --_bg: var(--_dark-bg);
    --_fg: var(--_dark-fg);
  }
}

接下来,添加一些样式,以帮助 SVG 图标适应空间大小。

.fab {
  …

  & > svg {
    inline-size: var(--_size);
    block-size: var(--_size);
    stroke-width: 3px;
  }
}

最后,从该按钮中移除点按突出显示标记,因为我们添加了自己的互动视觉反馈:

.fab {
  -webkit-tap-highlight-color: transparent;
}

迷你 FAB

本部分的目标是为 FAB 按钮创建一个变体。通过将一些 FAB 设置为小于默认操作,我们可以提升用户最常执行的操作。

迷你 FAB 标记

HTML 与 FAB 相同,但我们添加了“.mini”类,为 CSS 提供与变体中的钩子。

<button data-icon="heart" class="fab mini" title="Like action" aria-label="Like action">
  <svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">...</svg>
</button>
迷你 FAB 样式

由于使用了自定义属性,唯一需要的更改是调整 --_size 变量。

.fab.mini {
  --_size: 1.25rem;
}

两个 FAB 按钮堆叠且顶部按钮小于底部的按钮的屏幕截图。

无障碍功能

对于悬浮操作按钮的无障碍功能,要谨记最重要的一点是将其放置在页面的键盘流中。此演示仅包含 FAB,在键盘顺序和流方面没有可竞争的差异,这意味着它没有机会演示有意义的键盘流。在存在焦点竞争元素的场景中,我建议您深入思考用户应在该流程中的什么位置进入 FAB 按钮流程。

键盘互动演示

用户聚焦到 FAB 容器后,我们已经添加了 role="group"aria-label="floating action buttons",用于告知屏幕阅读器用户他们聚焦的内容。我已巧妙地将默认的 FAB 放置在首位,让用户最先找到主要操作。然后,我使用 flex-direction: column-reverse; 直观地对底部的主按钮进行排序,使其靠近用户手指的位置,以便于使用。这是一项非常实用的功能,因为默认按钮在视觉上非常醒目,对于键盘用户来说也是最先显示按钮,从而为他们提供非常相似的体验。

最后,别忘了对屏幕阅读器用户隐藏图标,并确保为其提供按钮标签,这样才不会成为谜团。这已经在 HTML 中通过 <svg> 上的 aria-hidden="true"<button> 上的 aria-label="Some action" 完成。

动画

您可以添加各种类型的动画,以增强用户体验。与其他 GUI 挑战一样,我们将设置几个自定义属性,以实现减少动作体验和全动作体验的意图。默认情况下,样式会假定用户想要减少动作,然后使用 prefers-reduced-motion 媒体查询将过渡值切换为完整动作。

具有自定义属性的减少动画策略

可以在以下 CSS 中创建了三个自定义属性:--_motion-reduced--_motion-ok--_transition。根据用户的偏好设置,前两个变量会保持适当的转换,最后一个变量 --_transition 将分别设置为 --_motion-reduced--_motion-ok

.fab {
  /* box-shadow and background-color can safely be transitioned for reduced motion users */
  --_motion-reduced:
    box-shadow .2s var(--ease-3),
    background-color .3s var(--ease-3);

  /* add transform and outline-offset for users ok with motion */
  --_motion-ok:
    var(--_motion-reduced),
    transform .2s var(--ease-3),
    outline-offset 145ms var(--ease-2);

  /* default the transition styles to reduced motion */
  --_transition: var(--_motion-reduced);

  /* set the transition to our adaptive transition custom property*/
  transition: var(--_transition);

  /* if motion is ok, update the adaptive prop to the respective transition prop */
  @media (prefers-reduced-motion: no-preference) {
    --_transition: var(--_motion-ok);
  }
}

完成上述操作后,就可以转换对 box-shadowbackground-colortransformoutline-offset 的更改,从而为用户提供良好的界面反馈,表明他们的互动已收到。

接下来,通过稍微调整 translateY 来为 :active 状态添加一点装饰,从而为按钮带来不错的按下效果:

.fab {
  …

  &:active {
    @media (prefers-reduced-motion: no-preference) {
      transform: translateY(2%);
    }
  }
}

最后,转换对按钮中的 SVG 图标所做的任何更改:

.fab {
  …

  &[data-icon="plus"]:hover > svg {
    transform: rotateZ(.25turn);
  }

  & > svg {
    @media (prefers-reduced-motion: no-preference) {
      will-change: transform;
      transition: transform .5s var(--ease-squish-3);
    }
  }
}

总结

现在你已经知道我是怎么做的,希望你怎么办 ‽ 🙂?

让我们来了解一下我们采用的方法多样化,并了解在 Web 上构建网站的所有方法。

只需创建一个演示,点击 tweet me 链接,我就会将其添加到下方的“社区混剪”部分中!

社区混剪作品

此处尚无可显示的内容。

资源