Кнопки и ссылки - самое частое место применения CSS-анимаций на сайте. Для кнопок обычно комбинируют background-color, transform (scale или сдвиг вверх) и box-shadow. Для ссылок эффектнее всего смотрится анимированное подчёркивание - но собрать его нужно правильным способом, иначе оно сломается на ссылке, которая переносится на несколько строк.
В этом уроке
Попробуй прямо сейчас: наведи на кнопку - она чуть поднимается вверх, слегка увеличивается и получает тень, всё это за счёт transform и box-shadow в паре с transition на базовом селекторе .btn (а не только внутри :hover). Наведи на ссылку - под ней слева направо "вырастает" линия: это не ::after, а анимация background-size у обычного background-image - ниже разберём, почему для ссылки это надёжнее.
Кнопки: комбо из фона, тени и scale
В hover-эффектах кнопок почти всегда комбинируют три свойства: background-color (смена цвета фона), transform - чаще всего scale() для лёгкого увеличения или translateY() для "приподнимания" вверх (обе техники разбирали в уроке про scale), и box-shadow, которая усиливает ощущение, что кнопка физически приподнялась над страницей. Ничего технически нового здесь нет - весь набор уже пройден в курсе, важна именно комбинация и правильное место transition.
Как и в уроке про transition при hover, transition обязательно пишется на БАЗОВОМ селекторе .btn, а не внутри .btn:hover - иначе плавным будет только "включение" эффекта при наведении, а "выключение" при уходе курсора произойдёт мгновенным скачком.
.btn {
transition: background-color .3s, transform .2s, box-shadow .3s;
}
.btn:hover {
background: #4338ca;
transform: translateY(-3px) scale(1.03);
box-shadow: 0 10px 20px rgba(79, 70, 229, .35);
} - transition на базовом классе, разные свойства могут анимироваться с разной длительностью.
.btn:hover {
background: #4338ca;
transform: translateY(-3px) scale(1.03);
box-shadow: 0 10px 20px rgba(79, 70, 229, .35);
transition: background-color .3s, transform .2s, box-shadow .3s;
} - transition написан только внутри :hover: при наведении эффект НЕ будет плавным (переход появляется только когда правило уже применилось), а при уходе курсора кнопка мгновенно вернётся в исходное состояние.
Ссылки: анимированное подчёркивание без сюрпризов на переносе строк
Самый очевидный способ анимировать подчёркивание ссылки - псевдоэлемент ::after с position: absolute и анимацией width или transform: scaleX(). Но у него есть скрытая проблема: если ссылка достаточно длинная и переносится на несколько строк, ::after - это ОДИН блок, привязанный к элементу целиком, а не к каждой строке отдельно, поэтому линия появляется только под последней строкой текста, а остальные строки остаются без подчёркивания вообще.
Надёжнее использовать не псевдоэлемент, а сам background элемента: линию рисует background-image: linear-gradient(currentColor, currentColor) - фактически сплошная полоса цвета текста, - а её ширину анимирует transition по background-size, растущему от 0% до 100%. Такой фон браузер рисует персонально для каждого строчного фрагмента ссылки, поэтому линия появляется под КАЖДОЙ строкой, даже если их несколько.
.link {
position: relative;
}
.link::after {
content: "";
position: absolute;
left: 0; bottom: -2px;
width: 100%; height: 2px;
background: currentColor;
} - на многострочной ссылке линия окажется только под последней строкой.
.link {
text-decoration: none;
background-image: linear-gradient(currentColor, currentColor);
background-position: 0% 100%;
background-repeat: no-repeat;
background-size: 0% 2px;
transition: background-size .3s;
}
.link:hover {
background-size: 100% 2px;
} - линия появляется под каждой строкой ссылки, даже если она переносится.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Писать transition только внутри :hover, а не на базовом селекторе | Наведение резкое или "включается" рывком, возврат в исходное состояние - мгновенный | Переносить transition на базовый селектор, как в уроке про transition при hover |
| Анимировать подчёркивание ссылки через ::after на потенциально многострочном тексте | Линия появляется только под последней строкой, остальные строки остаются без неё | Использовать background-image + background-size вместо ::after |
| Не оборачивать hover-эффекты в @media (any-hover: hover) там, где сайт открывают и с телефона | На тачскрине эффект "залипает" после тапа до следующего касания в другом месте | Оборачивать критичные hover-стили в @media (any-hover: hover), чтобы они срабатывали только там, где есть настоящее наведение |
| Совмещать сразу много transform-эффектов на кнопке (scale + translateY + rotate) без проверки | Кнопка "дёргается" или прыгает не туда, тяжело предсказать итоговое положение | Добавлять transform-функции по одной и проверять результат на каждом шаге |
Шпаргалка
| Запись | Что означает |
|---|---|
| transition на .btn (не на .btn:hover) | Плавными будут и наведение, и уход курсора |
| transform: translateY(-3px) scale(1.03); | Кнопка приподнимается и слегка увеличивается |
| ::after + position:absolute для подчёркивания | Ломается на многострочной ссылке - линия только под последней строкой |
| background-image: linear-gradient(currentColor, currentColor) + background-size | Надёжный способ подчёркивания - работает на любом числе строк |
| @media (any-hover: hover) { ... } | Hover-стили сработают только там, где есть настоящее наведение (мышь/трекпад) |
Коротко: отвечаем на вопросы
Почему на телефоне hover-эффект кнопки "залипает" после того, как убрал палец?
У сенсорного экрана физически нет состояния наведения - когда пользователь тапает по кнопке, браузер условно "включает" :hover и держит его до следующего касания в другом месте, а не отпускает сразу, как это делает мышь при уходе курсора. Современное решение - оборачивать hover-стили в @media (any-hover: hover) { ... }: это условие истинно только на устройствах, у которых есть настоящее наведение (мышь, трекпад), и ложно на чистом тачскрине, поэтому там залипать будет нечему.
Можно ли анимировать подчёркивание ссылки через ::after, если текст точно в одну строку?
Да, для однострочной ссылки способ через ::after работает без проблем - искажение проявляется только когда текст переносится на несколько строк. Но если заранее неизвестно, поместится ли ссылка в одну строку на всех экранах (а на мобильном экране длинная ссылка почти всегда перенесётся), надёжнее сразу использовать background-image + background-size - он работает одинаково в обоих случаях.