Давайте рассмотрим, как мы можем добавить всплывающую подсказку к вашему компоненту Vue, используя директиву и CSS.

В предыдущем посте мы рассмотрели, как создать доступный компонент всплывающей подсказки в Vue. Однако создание элемента для простой всплывающей подсказки может оказаться излишним. Следовательно, в этом посте мы рассмотрим другой подход к расширению компонента с помощью всплывающей подсказки, используя пользовательские директивы и CSS.

Что такое директива Vue?

В то время как компонент Vue является полностью автономным объектом приложения, директива больше похожа на декоративный и более легкий блок кода, который мы можем прикрепить к элементу, чтобы улучшить его функциональность и внешний вид в браузере. Это позволяет нам напрямую добавлять побочные эффекты или дополнительные модификации к соответствующим элементам DOM. Некоторыми распространенными примерами директивы Vue являются v-if, v-for и т. д., которые мы широко используем в любом приложении Vue.

Помимо встроенных директив, Vue также предоставляет нам API директив для создания нашей пользовательской директивы. Мы можем определить пользовательскую директиву как объект соответствующих хуков жизненного цикла или функцию, которая позволяет нам взаимодействовать с экземпляром элемента DOM, созданным для компонента.

Поскольку наша всплывающая подсказка в основном посвящена работе с классами и CSS и вряд ли изменится между обновлениями компонентов и монтированием, мы будем использовать функциональный подход. Давайте начнем, хорошо?

Предпосылки

У вас должно быть работающее приложение Vue (см. официальное руководство по настройке нового проекта Vue с помощью Vite) и базовое понимание CSS, а затем мы можем приступить к созданию нашей директивы всплывающей подсказки.

Создание директивы всплывающей подсказки

Чтобы создать пользовательскую директиву, мы сначала создаем новый файл /directives/tooltip.js в исходной папке нашего приложения со следующим кодом:

export function TooltipDirective(el, binding) {
}

Функция TooltipDirective принимает два аргумента — экземпляр элемента DOM el и объект binding, который содержит значение, переданное директиве, и другие модификаторы при ее использовании в компоненте. Для нашей всплывающей подсказки мы ожидаем, что binding.value будет объектом со следующими свойствами:

type TooltipOptions{
  text: string;
  position: 'top' | 'bottom' | 'left' | 'right';
}

Мы можем использовать binding.value для установки текста и положения всплывающей подсказки с помощью классов и атрибута data-tooltip. Мы также добавляем еще один класс — with-tooltip — к целевому элементу следующим образом:

export function TooltipDirective(el, binding) {
    el.setAttribute('data-tooltip', binding.value.text);
    el.classList.add('with-tooltip');

    const position = binding.value.position;
    el.classList.add(`tooltip--${position}`);
}

После монтирования (и обновления) компонента Vue активирует эту функцию и добавит новый атрибут data-tooltip с содержимым всплывающей подсказки и два дополнительных класса — with-tooltip и tooltip--[position] в сгенерированном элементе DOM, готовом к использованию. Но прежде чем мы сможем использовать всплывающую подсказку, нам нужно зарегистрировать нашу директиву в приложении. Мы сделаем это в следующем разделе.

Регистрация и использование всплывающей подсказки в приложении

В main.js нам нужно импортировать TooltipDirective и зарегистрировать его в экземпляре app с помощью метода app.directive следующим образом:

import { createApp } from 'vue'
import App from './App.vue'
import { TooltipDirective } from './directives/TooltipDirective'

const app = createApp(App)

app.directive('tooltip', TooltipDirective )

app.mount('#app')

Vue создаст соответствующую директиву с префиксом v-. Теперь мы можем использовать нашу директиву всплывающей подсказки в любом элементе с именем v-tooltip следующим образом:

<button 
  v-tooltip="{ text: 'I am the top tooltip', position: 'top' }"
>
  Top
</button>

А в браузере мы увидим элемент button, сгенерированный со следующим HTML:

<button 
  data-tooltip="Im the top tooltip" 
  class="with-tooltip tooltip--top"
>Top</button>

Однако всплывающая подсказка не будет отображаться при наведении или фокусировке на элементе. Нам нужно добавить немного CSS в наши классы всплывающих подсказок, что мы и сделаем дальше.

Добавление эффекта всплывающей подсказки с помощью CSS

Мы добавляем новый файл assets/tooltip.css в наше приложение и импортируем его в наш файл main.js, чтобы включить его в наше приложение:

/**main.js */
import './assets/tooltip.css';

В файле tooltip.css мы добавляем следующие правила CSS для оформления всплывающей подсказки:

.with-tooltip {
    position: relative;
}

.with-tooltip::before {
    content: attr(data-tooltip);
    opacity: 0;
    position: absolute;
    transition: opacity 2s;
    color: #ffffff;
    text-align: center;
    padding: 5px;
    border-radius: 2px;
    min-width: 120px;
    background: #5e5d5d;
    pointer-events: none;
    z-index: 1;
}

.with-tooltip::after {
    transition: opacity 2s;
    opacity: 0;
    content: "";
    border-width: 5px;
    border-style: solid;
    border-color: #5e5d5d transparent transparent transparent;
    position: absolute;
    z-index: 1;
    pointer-events: none;
}

Здесь мы устанавливаем положение целевого элемента на relative, чтобы позволить позиционированию всплывающей подсказки совпадать с ним. Для всплывающей подсказки мы используем псевдоэлемент ::before для отображения текста всплывающей подсказки и псевдоэлемент ::after для отображения стрелки всплывающей подсказки. Мы также установили некоторые свойства CSS, чтобы обеспечить отображение всплывающей подсказки, например следующие:

  • opacity: 0 к обоим элементам, чтобы скрыть их по умолчанию,
  • z-index: 1, чтобы всплывающая подсказка всегда находилась поверх других элементов, и
  • pointer-events: none, чтобы гарантировать, что браузер не поймает никаких событий, включая наведение курсора и фокусировку на элементах ::before и ::after.
  • content: attr(data-tooltip), чтобы установить текст всплывающей подсказки в ::before на значение атрибута data-tooltip с помощью файла attr().
  • content: "", чтобы очистить элемент ::after.

Мы также устанавливаем opacity: 1 для обоих элементов, когда пользователь наводит курсор или фокусируется на целевом элементе, как показано ниже:

.with-tooltip:hover::before, 
.with-tooltip:hover::after, 
.with-tooltip:focus::before, 
.with-tooltip:focus::after {
    opacity: 1;
}

И, наконец, мы установим положение всплывающей подсказки и стрелки в зависимости от положения всплывающей подсказки, используя псевдоэлементы ::before и ::after классов tooltip--top, tooltip--bottom, tooltip--left и tooltip--right, как показано ниже:

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

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

.tooltip--left::before {
    inset-block-end: 0%;
    inset-inline-end: 120%;
    min-height: 100%;
}

.tooltip--right::before {
    inset-block-end: 0%;
    inset-inline-start: 120%;
    min-height: 100%;
}

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

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

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

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

Обратите внимание, что мы используем логические свойства CSS inset-block и inset-inline для выравнивания всплывающей подсказки и стрелки с целевым элементом. Обратитесь к предыдущему руководству, чтобы узнать больше о логических свойствах и о том, как мы создаем кончик стрелки.

Наш элемент кнопки теперь будет иметь всплывающую подсказку при наведении курсора или фокусе на нем, как показано на снимке экрана ниже:

Достаточно прямолинейно. Теперь мы можем поиграть с позицией всплывающей подсказки, изменив свойство position значения, переданного в директиву. Однако может быть утомительно записывать свойство position каждый раз, когда мы используем директиву. Чтобы сделать его чище и организованнее, мы можем использовать модификаторы директив, и дальше мы увидим, как это сделать.

Использование директивных модификаторов для позиции

Мы можем определить позицию как модификаторы директивы, обозначая синтаксисом v-tooltip.[position], как в следующем примере:

<button v-tooltip.top="'I am the top tooltip'">Top</button>

Vue преобразует модификаторы в объект binding.modifiers, где каждое свойство является именем модификатора, а его значение равно true. В приведенном выше примере объект modifiers будет { top: true }.

В нашем TooltipDirective.js мы можем реализовать функцию для возврата подходящего класса позиции на основе объекта binding.modifiers следующим образом:

function getPositionClass(modifiers) {
    if (modifiers.top) {
        return 'top';
    } else if (modifiers.bottom) {
        return 'bottom';
    } else if (modifiers.left) {
        return 'left';
    } else if (modifiers.right) {
        return 'right';
    } 
        
    return 'top';
}

И мы можем реорганизовать TooltipDirective.js, чтобы использовать функцию getPositionClass(), как в следующем коде:

el.setAttribute("data-tooltip", binding.value?.text || binding.value);
el.classList.add("with-tooltip");

const position = binding.value.position || getPositionClass(binding.modifiers);
el.classList.add(`tooltip--${position}`);

Обратите внимание, что теперь мы получаем текстовое значение как из binding.value.text, так и из binding.value, чтобы пользователь мог передать текстовое значение как объект или строку.

Вот и все. Мы можем использовать нашу директиву всплывающей подсказки в пользу модификаторов, как в приведенном ниже примере:

<button v-tooltip.top="'I am the top tooltip'">Top</button>
<button v-tooltip.bottom="'I am the bottom tooltip'">Bottom</button>
<button v-tooltip.right="'I am the right tooltip'">Right</button>
<button v-tooltip.left="'I am the left tooltip'">Left</button>

И результат будет таким же, как предыдущий подход создания всплывающей подсказки как компонента:

Как насчет доступности? Он поставляется бесплатно с нашей директивой, без необходимости атрибутов aria-describedby или role:tooltip, поскольку текст всплывающей подсказки находится на том же элементе. Поскольку мы размещаем его на псевдоэлементе ::before, средство чтения с экрана сможет читать в следующем порядке: текст всплывающей подсказки, текст целевого элемента и роль целевого элемента. Разве это не потрясающе?

Краткое содержание

Пользовательская директива — отличная возможность для создания многоразовых надстроек на низком уровне, когда использование компонентов может быть излишним. Кроме того, используя правильный селектор элементов CSS, мы можем добавить дополнительные функции пользовательского интерфейса, такие как текстовая подсказка для элемента, с меньшим количеством кода.

Однако использование компонента всплывающей подсказки может быть преимуществом для более сложных сценариев, когда вы хотите отобразить всплывающую подсказку, содержащую не только текст, например абзац или изображение с некоторыми дополнительными стилями. В таких случаях я предлагаю комбинировать оба подхода, директиву для простых всплывающих подсказок и компонент для более сложных.

👉 Если хотите иногда быть со мной в курсе, подпишитесь на меня в Твиттере | Фейсбук.

👉 Узнайте больше о Vue из моей новой книги Learning Vue. Ранний выпуск уже доступен!

Понравился этот пост или нашел его полезным? Поделись 👇🏼 😉

Первоначально опубликовано на https://mayashavin.com.