Как работает CSS transition: плавные состояния для hover, focus и active

На чтение
8 мин
Дата обновления
05.11.2026

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

Для новичка здесь важны две идеи. Первая: переход запускается не сам по себе, а в момент изменения значения свойства. Вторая: браузер строит его не «между селекторами», а между старыми и новыми вычисленными значениями свойства.

Что запускает transition

Когда у элемента меняется состояние, браузер пересчитывает его стили. Если какое-то свойство получило новое вычисленное значение и это свойство указано в transition-property или в сокращении transition, изменение может пройти плавно, а не мгновенно.

Часто такой пересчет случается из-за псевдоклассов :hover, :focus и :active. Но механизм тот же и при добавлении класса, изменении inline-стиля или обновлении CSS из JavaScript.

Простой пример:

.button {
  background-color: #2563eb;
  transition: background-color 160ms ease;
}

.button:hover {
  background-color: #1d4ed8;
}

Пока кнопка в обычном состоянии, у нее одно вычисленное значение background-color. Когда указатель попадает на кнопку и начинает действовать :hover, вычисленное значение меняется. Так как для этого свойства задан transition, браузер строит промежуточные цвета за 160 миллисекунд.

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

На практике эти состояния обычно используют по-разному: :hover — для реакции на наведение, :focus — для визуального выделения элемента с фокусом, :active — для короткой реакции в момент нажатия. Это не разные механизмы перехода, а разные поводы пересчитать стили.

Как читать shorthand transition

Сокращенная запись собирает основные части перехода в одной строке:

transition: property duration timing-function delay;

Здесь:

  • property — какое свойство анимировать;
  • duration — сколько длится переход;
  • timing-function — как меняется скорость по ходу времени;
  • delay — через сколько начать переход.

Например:

.button {
  transition: background-color 160ms ease, transform 120ms ease-out;
}

Здесь у элемента настроены два перехода: один для фона, другой для трансформации. Это удобнее и надежнее, чем анимировать всё подряд.

Запись transition: all 200ms ease; действительно работает и удобна в черновиках или учебных примерах. Но в интерфейсах у нее есть минус: вместе с ожидаемыми свойствами она может анимировать и те, которые вы менять плавно не собирались. Достаточно, чтобы позже в другом правиле изменились padding, width или еще что-то из анимируемого списка.

Поэтому в прикладной верстке обычно лучше явно перечислять свойства:

.button {
  transition: background-color 160ms ease, box-shadow 160ms ease;
}

Так код проще читать, а поведение — проще предсказывать и отлаживать.

Еще одна тонкость — списки значений. Если для нескольких свойств вы указали разное количество длительностей или таймингов, браузер делает список подходящей длины: короткие значения повторяет, лишние отбрасывает. CSS от этого не становится невалидным, но код легко запутать, поэтому новичку лучше держать соответствие «одно свойство — одна настройка».

Какие свойства выбирать для плавных эффектов

Здесь полезно разделять два вопроса:

  • какие свойства удобно анимировать для типичных интерфейсных эффектов;
  • какие свойства обычно выгоднее для производительности.

Если говорить именно о производительности, чаще всего предпочтительны transform и opacity. Их обычно используют для сдвига, масштабирования, поворота, появления и затухания элемента.

Цветовые свойства вроде color, background-color и border-color тоже очень часто подходят для интерфейса: они хорошо читаются визуально, особенно в кнопках, ссылках и полях ввода. Но их не стоит ставить в один ряд с transform и opacity как с одинаково выгодными для рендеринга. Обычно это просто уместные и понятные свойства для мягкой обратной связи.

А вот свойства, связанные с раскладкой и геометрией, требуют осторожности: width, height, top, left, margin, padding. Они могут заставлять браузер выполнять более тяжелую работу по пересчету layout или перерисовке.

Часто удачный выборИспользовать осознанноЧастые источники проблем
opacitywidth, heightdisplay
transformtop, leftпереходы к auto
color, background-color, border-colorpadding, margin, сложные тенинеанимируемые свойства

Например, если нужно показать легкий «подъем» кнопки, обычно лучше использовать transform: translateY(-1px), а не менять top. Если нужен эффект появления, обычно проще и чище работать через opacity.

Паттерны для кнопок, ссылок и полей ввода

Хороший transition в интерфейсе обычно короткий и спокойный. Его задача — подтвердить действие и сделать смену состояния понятной, а не превратить страницу в набор спецэффектов.

Кнопка

.button {
  background-color: #2563eb;
  color: #fff;
  border: 0;
  transition:
    background-color 160ms ease,
    transform 120ms ease-out,
    box-shadow 160ms ease;
}

.button:hover {
  background-color: #1d4ed8;
  transform: translateY(-1px);
}

.button:focus {
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
}

.button:active {
  transform: translateY(0);
}

Здесь эффекты разделены по смыслу. Наведение слегка оживляет кнопку, фокус делает состояние заметным, а :active убирает часть подъема и создает ощущение нажатия. Такой набор обычно выглядит спокойнее, чем одинаково сильная анимация для всех состояний.

Ссылка

.link {
  color: #0f766e;
  text-decoration-color: currentColor;
  transition: color 140ms ease, text-decoration-color 140ms ease;
}

.link:hover,
.link:focus {
  color: #115e59;
}

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

Поле ввода

.field {
  border: 1px solid #cbd5e1;
  background-color: #fff;
  transition:
    border-color 140ms ease,
    box-shadow 140ms ease,
    background-color 140ms linear;
}

.field:hover {
  border-color: #94a3b8;
}

.field:focus {
  border-color: #2563eb;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}

У поля ввода важнее не hover, а заметный focus. Пользователь должен быстро понимать, где сейчас находится курсор и куда пойдет ввод с клавиатуры.

Если вы изучаете вёрстку по программе, где отдельным пунктом есть «Переходы», особенно полезно тренировать их именно на кнопках, ссылках и полях ввода: в этих элементах хорошо видно, как смена состояния запускает переход между вычисленными значениями.

Ограничения и типичные ошибки

Первая частая причина, почему transition «не работает», — свойство не анимируется. Не каждое CSS-свойство умеет плавно проходить промежуточные значения.

Вторая причина — свойство ведет себя дискретно. Это значит, что вместо плавной интерполяции браузер просто переключается со старого значения на новое в определенный момент. Классический пример — display.

По умолчанию рассчитывать на обычный плавный переход от display: none к display: block не стоит. Для поддерживаемых браузеров есть специальная схема с allow-discrete и начальным стилем через @starting-style, обычно в паре с opacity.

.panel {
  display: none;
  opacity: 0;
  transition:
    opacity 200ms ease,
    display 200ms allow-discrete;
}

.panel.is-open {
  display: block;
  opacity: 1;
}

@starting-style {
  .panel.is-open {
    opacity: 0;
  }
}

Это уже не базовый hover-эффект, а более продвинутая работа с состояниями элемента. Если нужна просто плавность при наведении, обычно лучше не начинать знакомство с transition с таких случаев.

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

Четвертая причина — значения плохо интерполируются. Если у свойств не совпадает структура значения или какая-то часть значения может меняться только дискретно, результат может отличаться от ожидаемого: от резкого скачка до полного отсутствия видимой плавности.

Как отлаживать переходы в DevTools

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

  1. Выделите элемент в панели Elements.
  2. Откройте вкладку Styles.
  3. Нажмите :hov и включите :hover, :focus или :active.
  4. Проверьте, какие правила применились и не переопределяются ли они другими селекторами.
  5. Откройте Computed, чтобы увидеть итоговые значения свойств, а не весь набор кандидатов из каскада.

Это особенно полезно, когда вы не уверены, меняется ли нужное свойство вообще. Иногда кажется, что «transition сломан», а на деле у элемента просто не изменилось то свойство, которое указано в списке переходов.

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

Еще один полезный инструмент — редактор easing-функции. В Styles можно открыть визуальный редактор для transition-timing-function и быстро проверить, не слишком ли резкий старт у анимации, не затянуто ли завершение и подходит ли здесь ease, ease-out или линейный ход.

А если переход выглядит дерганым, проверьте не только длительность, но и выбранное свойство. Очень часто проблема не в том, что стоит 150 миллисекунд вместо 250, а в том, что анимируется геометрия элемента там, где тот же визуальный эффект можно было собрать через transform или opacity.

Что важно запомнить

CSS transition — это мост между двумя вычисленными состояниями элемента. Псевдоклассы :hover, :focus и :active только создают момент, в который браузер пересчитывает стили и обнаруживает изменение.

Рабочий подход для начала такой: перечисляйте свойства явно, не злоупотребляйте all, для производительно чувствительных эффектов в первую очередь смотрите на transform и opacity, а цветовые изменения используйте как спокойную визуальную обратную связь. И обязательно проверяйте реальные итоговые значения через Styles и Computed в DevTools.