使用CSS有效地构建可访问的工具提示组件
#css #a11y #vue #tutorials

ToolTip是一条信息丰富且简短的消息,仅当用户与应用程序UI上的元素进行交互时,才会出现。作为一个常用的功能,它旨在为特定部分提供提示或其他描述性信息。对于交互式元素(例如按钮,尤其是图标按钮),使用工具提示可以改善组件的可访问性以及键盘和鼠标导航上的用户体验。虽然title仅出现在鼠标悬停上,但工具提示是提供有关键盘焦点上元素的重要信息的绝佳解决方案。

有两种方法可以为VUE中的特定元素创建工具提示:作为带有插槽的包装组件或目标组件上的自定义指令。

本文将探索第一种方法 - 使用VUE和CSS构建工具提示组件。是的,您不需要组件库来进行工具提示,因为它只会采用几行代码。

目录

先决条件

您需要有一个工作的VUE应用程序(请参阅the official guide for setting up a new Vue project with Vite)和对VUE和CSS的基本理解。

您的VUE项目准备就绪后,我们可以创建我们的工具提示组件。

构建具有插槽的Tooltip组件

我们的Tooltip包含一个包装元素,其中包括以下子元素:

  • 通过默认slot注入的任何子组件6
  • 文本字段充当注入的组件的工具提示。该组件接收此文本字段的内容,以使用text Props显示。

基于这些要求,我们将在我们的Tooltip.vue文件中添加以下代码:

<template>
    <div class="tooltip-wrapper">
        <slot />
        <span class="tooltip__text">{{ text }}</span>
    </div>
</template>
<script>
const props = defineProps({
    text: {
        type: String,
        required: true
    }
})
</script>

在上面的代码中,我们将带有tooltip-wrapper类的div用作:

的包装元素
  • 默认的slot作为组件占位符,
  • 一个span元素显示工具提示的text值,并具有类名称tooltip__text

和在<script>中,我们使用defineProps方法将text props定义为required字符串。

太好了。现在,我们为工具提示组件实现了基本结构。然后,我们可以在任何VUE组件中导入并使用Tooltip,如下所示:

<template>
    <Tooltip text="This is a tooltip">
        <button>Click me</button>
    </Tooltip>
</template>
<script setup>
import Tooltip from './Tooltip.vue'
</script>

然而,此时Tooltip不起作用,并且将始终显示目标元素下一步的工具提示,如下面的屏幕截图:

Basic Tooltip implement without visibility control

要使组件充分发挥功能,我们需要添加CSS样式并控制工具提示的位置和可见性,我们将在下一节中探索。

用CSS显示悬停的工具提示

默认情况下,当与元素没有相互作用时,工具提示应在UI上不可见。为此,我们将visibility设置为hiddenopacity to 0进行.tooltip__text类,并添加transition效果以使opacity平滑过渡。我们还将添加其他CSS属性,为工具提示提供一个不错的布局,并将包装器元素(.tooltip-wrapper)与display: inline-block设置为具有与其嵌套元素相同的宽度,如下所示:

.tooltip__text {
    visibility: hidden;
    opacity: 0;
    transition: opacity 1s;

    color: #ffffff;
    text-align: center;
    padding: 5px 0;
    border-radius: 2px;
    min-width: 120px;
    background: #5e5d5d;
}

.tooltip-wrapper {
    display: inline-block;
}

当用户徘徊在目标元素上时,我们希望显示工具提示。我们可以通过将opacity属性的值更改为1visibility的值来做到这一点,例如.tooltip-wrapper.tooltip-wrapper:hover上时,就像在以下代码中一样:

.tooltip-wrapper:hover .tooltip__text{
    visibility: visible;
    opacity: 1;
}

当用户在下面的屏幕截图中徘徊在目标元素上时,您可以看到该工具提示如何具有平滑的过渡效果:

Tooltip appearing only on hovering

由于我们将工具提示放在目标button元素下方,因此它将在不可见时留出空间,从而导致潜在的UI错误。我们可以通过使用position: absolute从页面布局的通常流中删除工具提示来避免此问题,从而删除与之生成的空间。我们还需要将z-index设置为1,以确保该工具提示在可见时始终重叠在其他元素之上。

.tooltip__text {
    /**... */
    position: absolute;
    z-index: 1;
}

我们必须为.tooltip-wrapper设置position: relative,以确保将工具提示相对于包装元素定位,如下:

.tooltip-wrapper {
    position: relative;
    display: inline-block;
}

现在,浏览器将正确显示该工具提示的下一步目标元素,如以下屏幕截图:

Tooltip appearing only on hovering on top of the paragraph

我们成功地创建了一个工具提示组件,显示了悬停在悬停的工具提示。但是,如果您使用键盘导航页面,则会注意到使用Tab键关注目标元素时,该工具提示是不可见的。因此,对于目标元素除了工具提示之外没有其他描述性标签的方案,它将引起视觉可访问性问题。将:focus伪元素添加到.tooltip-wrapper选择器将无济于事,因为此处的集中元素是目标元素-button,而不是其父级 - div。另外,VUE不支持从插槽中自动将诸如class之类的属性传递到目标组件,从而使组成正确的CSS选择器变得更加困难。

在这种情况下,我们使用::slotted()伪元素,我们将讨论下一步。

使用伪元素:slotted()显示焦点的工具提示2

:slotted() pseudo-element是一个CSS选择器,它允许我们选择传递到组件的默认插槽中的元素。它接受一个简单的组件选择器(无空间)作为其参数。对于我们的情况,我们可以使用Selector *选择目标元素,因此:slotted(*)

:slotted(*) {
}

然后,我们可以将:focus伪元素添加到:slotted(*)选择器中,并使用相邻的兄弟姐妹组合器(+)选择目标元素的立即兄弟姐妹,即.tooltip__text,并更改其opacity,以显示工具图,如IN in in in into以下代码:

:slotted(*):focus + .tooltip__text {
    visibility: visible;
    opacity: 1;
}

请注意,*将不用担心目标中的嵌套元素,因为:slotted()只能选择插槽的第一级子女。

现在,当使用键盘导航专注于目标button元素时,我们可以按预期看到该工具提示:

Tooltip appearing only on focusing

我们现在有一个功能齐全的工具提示组件,该组件显示了悬停和焦点上的工具提示。但是,浏览器将始终在目标元素下方显示工具提示,这不是那么可定制。以下部分将探讨如何使用道具和目标元素的位置定位工具提示。

用CS和道具定位工具提示

工具提示通常可以在一个元素周围出现四个位置:顶部,右,底部和左。我们首先在style部分中添加四个不同的CSS类选择器,每个COUDE62的位置都有不同的位置,如以下代码所示:

.tooltip--top {
}

.tooltip--bottom {
}

.tooltip--left {
}

.tooltip--right {
}

我们定义了一个新的prop -position,其默认值为bottom作为默认工具提示的位置。我们还添加了一个新的计算属性-tooltipClasses,以根据position Prop的值返回一组相关的类名称,如以下代码:

import { computed } from 'vue';

const props = defineProps({
    /**... */
    position: {
        type: String,
        default: 'bottom'
    },
})

const tooltipClasses = computed(() => ({
    'tooltip__text': true,
    [`tooltip--${props.position}`]: true
}))

然后,我们将计算的tooltipClasses绑定到.tooltip__text元素的class属性:

<span :class="tooltipClasses">{{ text }}</span>

现在,我们可以使用position prop将工具提示放置在left上,如以下代码:

<Tooltip text="This is a tooltip" position="left">
    <button>Click me</button>
</Tooltip>

vue将生成正确的类名,用于工具提示元素的tooltip--left tooltip__text

我们剩下的就是为每个职位添加相关的CSS规则。在本文中,我们使用相应的CSS逻辑属性来帮助基于目标元素的写作方向和文本方向和所需位置来控制工具提示的逻辑和物理位置,如下表所示:

位置 逻辑属性 描述
top inset-block-start 工具提示的顶部边缘和容器的顶部边缘之间的距离。
底部 inset-block-end 工具提示的底部边缘和容器的底部边缘之间的距离。
inset-inline-start 工具提示和容器与起始内联方向之间的距离,通常是从左边缘。
inset-inline-end 工具提示和容器与结尾内线方向之间的距离,通常是从右边缘。

以下代码块显示每个位置的CSS规则。

将工具提示定位到顶部

我们将为tooltip--top类选择器实施CSS规则:

.tooltip--top {
    inset-block-end: 120%;
    inset-inline-start: 50%;
    margin-inline-start: -60px;
}

在这里,我们使用inset-block-end: 120%inset-inline-start: 50%分别从其相对容器-.tooltip-wrapper(与目标元素具有相同偏移量的左边缘)放置上面的工具提示20%50%。我们还将margin-inline-start属性设置为-60px,以将工具提示60px移至左侧,这将水平居中,如以下屏幕截图所示:

所示。

Tooltip on the top

接下来,我们将实施底部位置的规则。

将工具提示定位到底部

与最高位置类似,我们保持左边缘偏移和边距,并使用inset-block-start: 120%tooltip--bottom类选择器将工具提示20%放置在其相对容器下方,如下:

.tooltip--bottom {
    inset-block-start: 120%;
    inset-inline-start: 50%;
    margin-inline-start: -60px;
}

和工具提示将从右侧出现,如下所示:

Tooltip on the bottom

接下来,我们将实施左位(或内线启动位置)的规则。

将工具提示放在左侧

对于左侧位置,我们使用inset-inline-end: 110%将工具提示10%从其相对容器的右>边缘取代,同时将两个使用inset-block-end: 0%对齐的底部边缘,如以下代码:
所示:

.tooltip--left {
    inset-block-end: 0%;
    inset-inline-end: 110%;
}

该工具提示将从左侧出现,如下所示:
Tooltip on the left

最后,我们将在右侧(或内联立场)上实施该位置的规则。

将工具提示放在右边

类似于左侧位置,我们将底部边缘对齐并用inset-inline-start: 110%左>左>左>左边缘取代工具提示。

.tooltip--right {
    inset-block-end: 0%;
    inset-inline-start: 110%;
}

该工具提示将从右侧出现,如下所示:

Tooltip on the right

就是这样。我们已经启用了具有四个不同位置的工具提示组件。随着工具提示在可自定义的位置显示,我们现在可以继续进行下一步 - 将箭头(三角形)添加到工具提示中,并使其看起来更好。

使用伪元素::after向工具提示添加箭头3

我们可以通过在.tooltip__text元素上使用:after伪元素来向工具提示添加箭头提示,并具有以下CSS规则:

  • content属性设置为空字符串以创建伪元素。
  • position属性设置为absolute,以从页面布局的正常流中删除元素,并使用position: relative set的祖先元素(在我们的情况下为.tooltip-wrapper)。
  • 使用border属性创建箭头形状,将border-style填充到solid并为border-width分配值。

以下代码演示了上述规则:

.tooltip__text::after {
    content: " ";
    position: absolute;
    border-width: 5px;
    border-style: solid;
}

在一个空元素(没有宽度/高度)中,当我们将border-width设置为5px时,整个零件将宽度和高度占据10px,并通过border-color属性充满边框的颜色。每个边界侧的颜色角将以三角形的形状重叠。例如,如果我们分别为顶部,右,底部和左侧设置border-color: red green blue pink,则该元素的颜色将如下所示:

A box with 4 different colors in shape of triangles using the border-color property

并将我们的箭头尖端作为三角形形状创建,我们通过将所需边框侧的颜色设置为工具提示的背景颜色和其余的透明来利用这种机制。以下是使用border-color时的三角形对每个不同侧的外观:

Triangles in different border sides

基于上述,我们可以为每个位置定义border-color的CSS规则,如下表所示:

位置 边框颜色
top #5e5d5d transparent transparent transparent
底部 transparent transparent #5e5d5d transparent
transparent transparent transparent #5e5d5d
transparent #5e5d5d transparent transparent

和工具提示,我们需要放置箭头尖。我们可以通过将inset-block-startinset-block-endinset-inline-startinset-inline-end属性设置为所需值来做到这一点,如以下代码所示:

.tooltip--left::after {
    inset-block-start: 50%;
    inset-inline-start: 100%;
    border-color: transparent transparent transparent #5e5d5d;
}

.tooltip--right::after {
    inset-block-start: 50%;
    inset-inline-end: 100%;
    border-color: transparent #5e5d5d transparent transparent;
}

.tooltip--top::after {
    inset-block-start: 100%;
    inset-inline-start: 50%;
    border-color: #5e5d5d transparent transparent  transparent;
}

.tooltip--bottom::after {
    inset-block-end: 100%;
    inset-inline-start: 50%;
    border-color: transparent transparent #5e5d5d transparent;
}

足够直接。现在,我们有一个功能齐全的工具提示组件,该组件支持自定义定位,显示悬停和焦点上的工具提示,并具有箭头提示,如以下屏幕截图:

tooltip appearing on hovering with arrow tip, from four directions

我们完成了吗?不,我们仍然需要使屏幕读取器访问该工具提示。让我们接下来做。

向筛选读者宣布工具提示内容

使用工具提示导航到元素时,屏幕读取器需要知道工具提示与其关联,并宣布其与组件的内容。为此,我们使用工具提示使用目标元素上的aria-describedby属性,并传递代表工具提示元素的id的唯一字符串,使用tooltip-id prop,如下所示:

<Tooltip text="I'm the top tooltip" position="top" :tooltip-id="tooltipsIds.top">
 <button :aria-describedby="tooltipsIds.top">Top</button>
</Tooltip>

我们将调整Tooltip组件以接受tooltipId作为prop,并将其应用于包含工具提示内容的span元素上的id属性,如下:

<span :class="tooltipClasses" role="tooltip" :id="tooltipId">{{ text }}</span>

请注意,我们还必须将元素的role分配为tooltip,以供屏幕读取器拾取。这样做有助于防止工具提示在关注目标元素时一次宣布两次,并且在屏幕读取器到达工具提示时一次。我们还可以启用inert属性,以确保浏览器将工具提示排除在可访问性树顺序中并忽略其中的任何事件,包括辅助技术的事件。

<span :class="tooltipClasses" role="tooltip" :id="tooltipId" inert>{{ text }}</span>

下面是该工具提示在导航到Top按钮时如何与屏幕读取器一起工作的演示:

VoiceOver reads out the tooltip and the button description on focus

现在,对于屏幕读取器等辅助技术,我们的工具提示组件现在可以访问。确实,我们可以做更多的事情来改善组件,例如允许在工具提示中定制组件等等。目前,我们有一个功能齐全且易于访问的工具提示组件,可以在VUE项目中使用。

资源

您可以在GitHub repo上找到本教程的源代码和/或了解有关本文中涵盖的一些主题的更多信息:

概括

使用组件库似乎很方便,但是在使用组件时可以选择更好的选择。有时,最好创建组件以对实现和自定义更具控制权。我们已经学习了如何使用VUE和CSS从头开始创建工具提示组件。从本教程中,您可以将组件扩展到具有更多功能,例如颜色自定义,动画等。

下一步是什么?如何实施第二种方法来创建工具提示为vue.js指令并查看您更喜欢哪个?

ð如果您想赶上我有时候,请在Twitter上关注我| Facebook

喜欢这篇文章还是发现它有帮助?分享ðð¼ð