Как сделать анимацию в HTML с помощью CSS
Как сделать анимацию в HTML без лишнего JavaScript? Используйте transition для плавной смены двух состояний, а animation и @keyframes – для самостоятельных сценариев. Вы соберете hover-эффекты, пульсацию, появление при прокрутке и запуск по клику. Код учитывает производительность и настройку prefers-reduced-motion.
- Как работает анимация в HTML: transition и animation
- Плавные эффекты при наведении на transition
- Своя анимация на @keyframes
- Готовые анимации из Animate.css
- Запуск анимации по клику и при прокрутке
- Производительность плавной CSS-анимации
- Анимация и доступность: prefers-reduced-motion
- Почему CSS-анимация не срабатывает
Как работает анимация в HTML: transition и animation
CSS-анимация меняет свойства элемента во времени. Выбирайте механизм по тому, кто запускает движение и сколько состояний оно содержит.
| Параметр | transition | animation |
|---|---|---|
| Запуск | После изменения состояния, например при наведении | После назначения свойства или класса |
| Состояния | Исходное и конечное | Два кадра или последовательность кадров |
| Повторы | Переход сам не повторяется | animation-iteration-count задает повторы |
| JavaScript | Скрипт меняет класс или свойство | Скрипт запускает и отслеживает сценарий |
Берите transition, когда элемент отвечает на действие пользователя. Выбирайте animation, когда эффект стартует сам или проходит несколько фаз.
Синтаксис описан в справочниках MDN по transition и MDN по animation.
Плавные эффекты при наведении на transition
Переход состоит из свойства, длительности, функции времени и задержки. Указывайте конкретные свойства, чтобы изменение другого стиля не запускало лишний эффект.
.card {
transition-property: transform, box-shadow;
transition-duration: 300ms;
transition-timing-function: ease;
transition-delay: 0ms;
}
.card {
transition: transform 300ms ease 0ms;
}Запись 300ms равна 0.3s. Без длительности переход получает значение 0s и остается незаметным.
Квадрат превращается в круг
Контейнер обрезает изображение, а border-radius меняет форму рамки. Ссылка получает такой же эффект при фокусе с клавиатуры.
<a class="shape-demo" href="photo.jpg">
<img src="photo.jpg" alt="Фотография в квадратной рамке">
</a>.shape-demo {
display: block;
width: 200px;
height: 200px;
overflow: hidden;
border-radius: 16px;
transition: border-radius 1s ease;
}
.shape-demo img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
@media (hover: hover) {
.shape-demo:hover {
border-radius: 50%;
}
}
.shape-demo:focus-visible {
border-radius: 50%;
outline: 3px solid #2457d6;
outline-offset: 4px;
}
Увеличение изображения через transform
Свойство transform: scale() увеличивает изображение внутри контейнера. Размер контейнера не меняется, поэтому соседние элементы не сдвигаются.
<a class="zoom-demo" href="photo.jpg">
<img src="photo.jpg" alt="Фотография для увеличения">
</a>.zoom-demo {
display: block;
width: 320px;
max-width: 100%;
overflow: hidden;
}
.zoom-demo img {
display: block;
width: 100%;
transition: transform 400ms ease;
}
@media (hover: hover) {
.zoom-demo:hover img {
transform: scale(1.08);
}
}
.zoom-demo:focus-visible img {
transform: scale(1.08);
}
.zoom-demo:focus-visible {
outline: 3px solid #2457d6;
outline-offset: 4px;
}
Наклон и черно-белое изображение
rotate() поворачивает элемент на заданный угол. Функция grayscale() убирает цвет изображения.
<a class="tilt-demo" href="photo.jpg">
<img src="photo.jpg" alt="Цветная фотография">
</a>.tilt-demo {
display: inline-block;
max-width: 100%;
transition: transform 300ms ease, filter 300ms ease;
}
.tilt-demo img {
display: block;
width: 200px;
max-width: 100%;
}
@media (hover: hover) {
.tilt-demo:hover {
transform: rotate(6deg);
filter: grayscale(100%);
}
}
.tilt-demo:focus-visible {
transform: rotate(6deg);
filter: grayscale(100%);
outline: 3px solid #2457d6;
outline-offset: 4px;
}

Тень, hover для мыши и фокус для клавиатуры
Псевдокласс :hover реагирует на указатель. Сенсорный экран может не поддерживать наведение, поэтому ограничьте правило медиазапросом (hover: hover). Для клавиатуры добавьте :focus-visible.
<button class="button" type="button">Попробовать эффект</button>.button {
padding: 12px 20px;
border: 1px solid #2457d6;
border-radius: 8px;
transition: transform 200ms ease, box-shadow 200ms ease;
}
@media (hover: hover) {
.button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgb(0 0 0 / 20%);
}
}
.button:focus-visible {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgb(0 0 0 / 20%);
outline: 3px solid #2457d6;
outline-offset: 3px;
}Своя анимация на @keyframes
Блок @keyframes описывает кадры, а свойство animation подключает их к элементу. Используйте from и to для двух состояний или проценты для последовательности кадров.
Полный синтаксис смотрите в справочнике MDN по @keyframes.
| Свойство | Назначение |
|---|---|
animation-name | Имя блока @keyframes |
animation-duration | Длительность одного цикла |
animation-timing-function | Темп движения |
animation-delay | Задержка старта |
animation-iteration-count | Число повторов или infinite |
animation-direction | Направление проигрывания |
animation-fill-mode | Состояние до старта и после завершения |
animation-play-state | running или paused |
Шортхенд объединяет настройки в одной строке. Первое время задает длительность, второе – задержку.
.message {
animation: fade-in 600ms ease-out 100ms 1 normal both running;
}Значение forwards сохраняет завершающий кадр. Значение backwards применяет начальный кадр во время задержки. Значение both объединяет оба поведения.
Плавное появление элемента
<article class="message">
<h2>Сообщение загружено</h2>
<p>Карточка появляется со сдвигом.</p>
</article>@keyframes fade-in {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message {
animation: fade-in 600ms ease-out 100ms 1 normal both;
}Карточка ждет 100 миллисекунд, затем появляется за 600 миллисекунд. Перезагрузка страницы запускает эффект снова.
Бесконечная пульсация кнопки
<button class="pulse-button" type="button">Попробовать пульсацию</button>@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.pulse-button {
animation: pulse 1.8s ease-in-out infinite;
}
.pulse-button:focus-visible {
animation-play-state: paused;
}Кнопка увеличивается в середине цикла. Фокус клавиатуры приостанавливает декоративное движение.
Спиннер загрузки
<p role="status">
<span class="spinner" aria-hidden="true"></span>
Загрузка…
</p>@keyframes spin {
to {
transform: rotate(360deg);
}
}
.spinner {
display: inline-block;
width: 32px;
height: 32px;
border: 4px solid #d7dbe5;
border-top-color: #2457d6;
border-radius: 50%;
animation: spin 800ms linear infinite;
}Кольцо делает оборот за 800 миллисекунд. После загрузки уберите статус из интерфейса.
Готовые анимации из Animate.css
Animate.css содержит готовые CSS-классы. Библиотека использует префикс animate__ и базовый класс animate__animated. Исходный код хранится в репозитории Animate.css на GitHub.
Подключение через npm и локальный файл
Если проект использует сборщик, установите пакет через npm. Выполните команду в терминале из папки проекта.
npm install animate.cssИмпортируйте библиотеку во входном JavaScript-файле.
import 'animate.css';Для страницы без сборщика скачайте файл библиотеки с официальной страницы Animate.css и подключите его в head. Скопированный файл можно положить в папку css.
<link rel="stylesheet" href="css/animate.min.css">После подключения добавьте базовый класс и имя эффекта к элементу.
<h2 class="animate__animated animate__bounce">
Заголовок подпрыгивает
</h2>Заголовок проигрывает эффект при загрузке страницы. Удаление классов возвращает обычное состояние.
Задержка, скорость и повторы
Класс animate__delay-2s задает задержку 2 секунды. Классы animate__slow и animate__fast меняют скорость. Класс animate__repeat-2 повторяет эффект 2 раза.
<p class="animate__animated animate__bounce animate__delay-2s animate__slow animate__repeat-2">
Медленный эффект с задержкой
</p>
<p class="animate__animated animate__bounce animate__fast">
Быстрый эффект без задержки
</p>Одному элементу назначайте только один класс скорости. Собственные значения задавайте через CSS-переменную --animate-duration.
Поочередное появление элементов
Разные задержки запускают пункты списка по очереди. Длительность меняется без правки файла библиотеки.
<ul class="features">
<li class="animate__animated animate__fadeInUp animate__delay-1s">Первый пункт</li>
<li class="animate__animated animate__fadeInUp animate__delay-2s">Второй пункт</li>
<li class="animate__animated animate__fadeInUp animate__delay-3s">Третий пункт</li>
</ul>.features {
--animate-duration: 700ms;
}Первый пункт начнет появление через 1 секунду. Второй стартует через 2 секунды, третий – через 3 секунды. Каждый эффект длится 700 миллисекунд.
Запуск анимации по клику и при прокрутке
JavaScript добавляет класс, удаляет его для перезапуска и слушает событие animationend. Один CSS-класс хранит сценарий, а скрипт отвечает за момент запуска.
Добавление класса по клику
<button class="run-button" type="button">Запустить</button>
<div class="box">Готово к запуску</div>
<p class="run-status" role="status"></p>@keyframes pop {
0% {
transform: scale(.8);
opacity: 0;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.box.is-active {
animation: pop 400ms ease-out both;
}const button = document.querySelector('.run-button');
const box = document.querySelector('.box');
const status = document.querySelector('.run-status');
button.addEventListener('click', () => {
box.classList.remove('is-active');
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
status.textContent = 'Готово, движение отключено';
return;
}
void box.offsetWidth;
box.classList.add('is-active');
status.textContent = 'Анимация идет';
});
box.addEventListener('animationend', (event) => {
if (event.animationName === 'pop') {
status.textContent = 'Анимация завершена';
}
});Удаление класса и чтение offsetWidth фиксируют промежуточное состояние. Повторное добавление класса запускает эффект снова.
Чтение offsetWidth может вызвать синхронный пересчет раскладки. Используйте такой перезапуск для отдельных действий, а не для каждого кадра.
Появление при прокрутке
IntersectionObserver сообщает, когда элемент попадает в область просмотра. После первого запуска наблюдатель прекращает работу для этого элемента.
<section class="reveal">
<h2>Раздел страницы</h2>
<p>Блок появляется при прокрутке.</p>
</section>@keyframes reveal-in {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.reveal.is-pending {
opacity: 0;
transform: translateY(24px);
}
.reveal.is-visible {
animation: reveal-in 600ms ease-out both;
}const sections = document.querySelectorAll('.reveal');
if ('IntersectionObserver' in window) {
const observer = new IntersectionObserver((entries, currentObserver) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.remove('is-pending');
entry.target.classList.add('is-visible');
currentObserver.unobserve(entry.target);
});
});
sections.forEach((element) => {
element.classList.add('is-pending');
observer.observe(element);
});
}Скрывающий класс добавляет скрипт только после проверки API. Поэтому без JavaScript содержимое остается видимым.
Web Animations API дает альтернативный способ: вызов element.animate() запускает кадры из JavaScript.
Производительность плавной CSS-анимации
Изменение размеров заставляет браузер пересчитать геометрию страницы. Такой пересчет называют reflow, а повторную отрисовку областей – repaint.
Для движения выбирайте transform и opacity. Они не меняют раскладку соседних элементов и часто обрабатываются на этапе композиции.
<button class="smooth-card" type="button">Плавное движение</button>.smooth-card {
transition: transform 250ms ease, opacity 250ms ease;
}
@media (hover: hover) {
.smooth-card:hover {
transform: translateY(-6px);
opacity: .9;
}
}
.smooth-card:focus-visible {
transform: translateY(-6px);
opacity: .9;
}Анимация width, height, top и left может запускать перерасчет раскладки. Тени, фильтры и скругления иногда требуют дополнительной отрисовки.
Не назначайте will-change сотням элементов. Такое правило расходует память и иногда ухудшает производительность.
- Откройте Chrome DevTools на странице с эффектом.
- Перейдите на вкладку Performance и начните запись.
- Запустите анимацию и остановите запись.
- Выберите участок движения и найдите события Layout и Paint.
- Замените изменение размеров на
transform, повторите запись и сравните результат.
Длинные события Layout или Paint занимают время кадра и могут объяснить рывки. Панель Animations помогает увидеть кадры и задержки, а Performance показывает нагрузку на раскладку.
Сведения о поддержке браузерами собраны в базе Can I Use. В примерах используются стандартные transition, animation, transform и filter без вендорных префиксов.
Анимация и доступность: prefers-reduced-motion
Некоторые пользователи уменьшают движение в системных настройках. Медиазапрос prefers-reduced-motion позволяет остановить декоративные эффекты и сохранить смысловую обратную связь.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 1ms !important;
animation-delay: 0ms !important;
animation-iteration-count: 1 !important;
transition-duration: 1ms !important;
transition-delay: 0ms !important;
scroll-behavior: auto !important;
}
.pulse-button,
.spinner,
.box.is-active,
.reveal.is-visible {
animation: none !important;
}
.reveal.is-pending,
.reveal.is-visible {
opacity: 1;
transform: none;
}
}После добавления правила пульсация и вращение остановятся, а скрытые блоки останутся видимыми. Для рабочего проекта отключайте эффекты адресно, чтобы не убрать важные состояния интерфейса.
Текстовый статус «Загрузка…» сохраняет смысл спиннера без движения. Подробности о медиазапросе описаны в документации MDN по prefers-reduced-motion.
Почему CSS-анимация не срабатывает
Проблему обычно вызывают имя, время или состояние элемента. Проверьте эти причины по порядку.
- Не задана длительность. Добавьте
animation-durationили время в шортхенд. Значение по умолчанию равно0s. - Опечатка в имени. Значение
animation-nameдолжно совпадать с именем блока@keyframes. - Анимируется
display. Переключение происходит дискретно. Для плавного исчезновения используйтеopacity. - Высота равна
auto. Для простого перехода задайте числовые значения начальной и конечной высоты. - Класс уже добавлен. Повторное присваивание того же класса не перезапускает эффект. Сначала удалите класс, затем добавьте его снова.
- Несколько анимаций меняют
transform. Объедините преобразования в одних кадрах или распределите их между элементом и оберткой. - Эффект работает только на hover. Добавьте
:focus-visibleи обработчик клика для действия на сенсорном экране. - JavaScript не нашел элемент. Проверьте селектор и подключите скрипт после разметки или с атрибутом
defer. - Движение отключено настройками. Убедитесь, что правило
prefers-reduced-motionне остановило эффект намеренно.
Совет от нашей редакции: сначала соберите короткий эффект на transform и opacity, затем добавляйте задержки и декоративные кадры. Такой порядок упрощает поиск ошибок и сохраняет отзывчивость интерфейса.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- overflow hidden что это и почему обрезка не работает 6 мин чтения
- 50 примеров CSS3‑кнопок с эффектами для сайта 5 мин чтения
- 17 эффектных CSS‑анимаций с готовыми примерами 3 мин чтения
- 19 CSS‑спиннеров и прелоадеров для загрузки 5 мин чтения
Комментарии