Как сделать анимированное подчеркивание ссылок на CSS

Реализовать подчеркивание на CSS непросто. Сложности возникают, если вам нужно нечто более оригинальное, чем стандартный text-decoration: underline. Существует немало различных методов — но, к сожалению, все они имеют серьезные недостатки.

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

Неприятность, с которой я столкнулся, заключалась в следующем — в моем блоге ссылки часто переносятся на следующую строчку — а это означает, что часть ссылки остается неподчеркнутой. К слову, в блоге Кэсси впечатляют не только ссылки — там прекрасно все, от кода до контента.

Задача

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

Применение фона

Существует сразу несколько различных способов подчеркнуть текстовый фрагмент. Я остановился на методе, который удовлетворяет всем критериям, и использует CSS свойство background-image. Фон background-image можно залить сплошным цветом, применяя функцию linear-gradient так, чтобы первый цвет перетекал во второй такой же.

Зачем использовать background-image, а не background-color, если предполагается заливка сплошным цветом? Дело в том, что background-image предоставляет больше свойств для манипуляций с фоном.

Размер фона ограничен по высоте и ширине и полностью заполняет ширину элемента ссылки — с помощью указания параметров background-size 2px и 100% соответственно. Это приводит к сплошному заполнению фона, поэтому я установил no-repeat для background-repeat.

Подчеркивание окажется сверху ссылки. Расположение линии меняем на нижнюю левую границу, устанавливая 0 100% для background-position — тогда она окажется внизу, под текстом ссылки.

Два фона

Для использования нескольких фоновых изображений и манипулирования их свойствами, установите для background-* несколько значений, разделенных запятыми. Первая запись из списка окажется верхним слоем, последующие будут нижними слоями. Фон элемента анкора будет сплошным черным (#000000). Белый (#FFFFFF) фон скрыт под ним:

a {
  background-image: linear-gradient(#000000, #000000), linear-gradient(#ffffff, #ffffff);
}

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

a {
  color: #dfe5f3;
  text-decoration: none;
  background-image: linear-gradient(rgb(176, 251, 188), rgb(176, 251, 188)),
    linear-gradient(#feb2b2, #feb2b2);
  background-size: 100% 2px, 100% 2px;
  background-position: 100% 100%, 0 100%;
  background-repeat: no-repeat, no-repeat;
}

Переход для background-size

Обратили внимание, чем отличаются параметры background-position, хотя визуально разницы нет? Один параметр привязан к левой стороне, второй — к правой.

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

a {
  color: #dfe5f3;
  text-decoration: none;
  background-image: linear-gradient(rgb(176, 251, 188), rgb(176, 251, 188)),
    linear-gradient(#feb2b2, #feb2b2);
  background-size: 100% 2px, 0 2px;
  background-position: 100% 100%, 0 100%;
  background-repeat: no-repeat;
  transition: background-size 2s linear;
}

a:hover {
  background-size: 0 2px, 100% 2px;
}

Три фона

Задача почти выполнена — осталось добавить промежуток между подчеркиваниями. Пробел между линиями можно имитировать, передвигая разделительный блок, цвет которого совпадает с фоновым. Что собой представляет подобный цветной разделитель? Верно, еще один фон. Что может быть лучше, чем два фона? Только три фона.

Этот фон я размещу поверх двух первых, указывая его первым в списке свойств для background-image. Не забудьте, что первое значение в списке свойств background-* теперь также относится к новому фону background-image. Высоту и ширину задаем с помощью background-size. Высота идентична параметрам предыдущих фонов (2px в нашем случае), а ширина установлена небольшая — 20px.

Переход для background-position

Разделительный фрагмент должен быть невидимым до наведения курсора, поэтому позиция фона background-position установлена отрицательной — это разместит разделитель слева от анкора, и таким образом полностью его скроет. В момент наведения курсора разделитель должен сдвинуться по подчеркиванию в противоположном направлении — и снова исчезнуть из вида.

Для вычисления обеих позиций воспользуемся функцией calc():

a {
  color: #dfe5f3;
  text-decoration: none;
  background-image: linear-gradient(#222b40, #222b40), linear-gradient(
      rgb(176, 251, 188),
      rgb(176, 251, 188)
    ), linear-gradient(#feb2b2, #feb2b2);
  background-size: 20px 2px, 100% 2px, 0 2px;
  background-position: calc(20px * -1) 100%, 100% 100%, 0 100%;
  background-repeat: no-repeat;
  transition: background-size 2s linear, background-position 2s linear;
}

a:hover {
  background-size: 20px 2px, 0 2px, 100% 2px;
  background-position: calc(100% + 20px) 100%, 100% 100%, 0 100%;
}

Теперь анимированное подчеркивание работает так, как и требовалось. Мы благодарим Джея Томпкинса — настоящего волшебника в сфере CSS анимации. Именно он предложил метод анимации, основанный на манипуляции со свойством background-position.