Олег Марков
Анимации появления enter animations - практическое руководство
Введение
Анимации появления (enter-animations) — это анимации, которые срабатывают в момент, когда элемент впервые появляется в интерфейсе: добавляется в DOM, становится видимым, монтируется в дереве компонентов или входит в область видимости при скролле.
Сюда относятся эффекты, когда:
- модальное окно плавно выезжает из центра;
- новый элемент списка аккуратно «вещается» вниз;
- при переходе между экранами новый экран въезжает справа;
- блок становится видимым при прокрутке страницы.
Enter-animations помогают:
- структурировать внимание пользователя;
- сглаживать переходы между состояниями интерфейса;
- делать появление элементов предсказуемым и менее «резким».
Ниже я разберу, как проектировать и реализовывать анимации появления в разных средах: на «голом» CSS, с помощью JavaScript, а также кратко коснусь реализации во фреймворках.
Что такое enter-animations и чем они отличаются от других видов анимаций
Классификация интерфейсных анимаций
Для начала полезно разделить анимации по их роли:
- Enter-animations — анимации при появлении элемента.
- Exit-animations — анимации при исчезновении элемента.
- Transition animations — анимации при изменении состояния уже видимого элемента (например, hover, изменение размера, цвета).
- Continuous / loop animations — постоянные анимации, не привязанные к событию появления (например, индикатор загрузки).
Enter-animations обычно:
- запускаются один раз при монтировании или первом появлении;
- синхронизируются с логикой отображения (например, при изменении состояния isOpen);
- работают совместно с exit-animations, если элемент не просто исчезает мгновенно.
Основные принципы хороших enter-animations
Давайте кратко сформулируем принципы, на которые стоит ориентироваться:
- Предсказуемость — пользователь должен понимать, откуда «пришел» элемент и куда он «привязан» в интерфейсе.
- Скорость — анимация не должна замедлять взаимодействие.
- Скромность — лучше недоанимировать, чем перенасытить движением.
- Функциональность — анимация помогает понять структуру интерфейса, а не просто «украшает».
Сейчас посмотрим, как это выразить в конкретных настройках.
Временные параметры enter-анимаций
Длительность и задержка
Для большинства интерфейсных enter-анимаций подходят такие диапазоны:
- 150–250 мс — быстрые, почти незаметные появление небольших элементов (tooltip, badge).
- 200–300 мс — стандартные enter-анимации для блоков, карточек, модалок.
- 300–450 мс — более крупные переходы (смена экрана, полностраничные панели).
Пример базового CSS с визуально комфортной длительностью:
.modal-enter {
opacity: 0; /* Стартующее состояние - невидимая модалка */
transform: scale(0.95); /* Чуть уменьшена, чтобы создать эффект "приближения" */
}
.modal-enter-active {
opacity: 1; /* Конечное состояние - полностью видимая модалка */
transform: scale(1); /* Нормальный размер */
transition: opacity 220ms ease-out, transform 220ms ease-out;
/* duration 220ms - быстрая, но заметная анимация появления */
}
Задержку появления (transition-delay) лучше использовать очень аккуратно. Она может быть полезна в таких случаях:
- последовательное появление элементов (staggered animations);
- синхронизация с другими анимациями.
Но важно помнить: любая задержка — это искусственное замедление интерфейса.
Тайминг-функции (easing)
Тайминг-функция определяет, как скорость анимации меняется во времени. Для enter-animations чаще всего используют:
- ease-out — быстрое начало, плавное замедление к концу;
- cubic-bezier с акцентом на мягкое завершение.
Пример:
.fade-in {
opacity: 0;
transform: translateY(10px);
}
.fade-in-active {
opacity: 1;
transform: translateY(0);
transition:
opacity 180ms ease-out, /* Плавное затухание к концу */
transform 220ms cubic-bezier(0.18, 0.89, 0.32, 1.28);
/* Чуть более "пружинистое" движение вверх */
}
Здесь я использую немного усиленный cubic-bezier для более живого движения. Вы можете подобрать свои значения, экспериментируя в devtools.
Базовые паттерны появлений
Простейшее появление через прозрачность (fade-in)
Самый простой и универсальный паттерн — постепенное увеличение прозрачности.
.fade-enter {
opacity: 0; /* Элемент невидим в начальном состоянии */
}
.fade-enter-active {
opacity: 1; /* Элемент становится полностью видимым */
transition: opacity 200ms ease-out;
}
Такой вариант хорошо подходит, когда:
- элемент появляется на месте уже ожидаемого пользователем контента;
- нет необходимости подчеркивать направление движения.
Минус: без движения элемент может казаться «плоским». Часто лучше добавить небольшое смещение или масштаб.
Появление с легким смещением (fade + slide)
Давайте добавим небольшое движение сверху или снизу, чтобы усилить ощущение «прихода» элемента.
.fade-slide-up-enter {
opacity: 0;
transform: translateY(8px); /* Элемент чуть ниже своей итоговой позиции */
}
.fade-slide-up-enter-active {
opacity: 1;
transform: translateY(0);
transition:
opacity 200ms ease-out,
transform 220ms ease-out;
}
Такой паттерн хорошо «заходит» для:
- карточек в списках;
- уведомлений;
- небольших панелей и попапов.
Появление со скейлом (zoom-in)
Эффект масштабирования добавляет ощущение «приближения» элемента к пользователю.
.zoom-in-enter {
opacity: 0;
transform: scale(0.96); /* Немного уменьшен */
}
.zoom-in-enter-active {
opacity: 1;
transform: scale(1);
transition:
opacity 200ms ease-out,
transform 200ms cubic-bezier(0.18, 0.89, 0.32, 1.1);
/* Чуть ускоренное приближение к нормальному размеру */
}
Так можно оформлять:
- модальные окна;
- диалоговые панели;
- всплывающие карточки с подробной информацией.
Комбинированные паттерны
Вы можете объединять несколько эффектов:
- fade + slide + scale;
- изменение opacity + blur (если позволяет производительность);
- сложные keyframes (особенно в брендовых анимациях).
Но лучше удерживаться в рамках 1–2 простых эффектов для стандартных интерфейсных элементов.
Реализация enter-animations на CSS без фреймворков
Механика: начальный и активный классы
На чистом CSS enter-анимация обычно строится на идее «двух состояний»:
- класс с начальными стилями (enter);
- класс с конечными стилями и переходами (enter-active).
Алгоритм:
- Вы добавляете элемент в DOM с классом base и enter.
- На следующем тике (после reflow) добавляете класс enter-active.
- После окончания анимации убираете класс enter (при необходимости).
Смотрите, вот пример на JavaScript:
<div id="container"></div>
.card {
width: 200px;
padding: 16px;
background: #fff;
border-radius: 8px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}
/* Начальное состояние появления */
.card-enter {
opacity: 0;
transform: translateY(10px);
}
/* Активная фаза анимации появления */
.card-enter-active {
opacity: 1;
transform: translateY(0);
transition:
opacity 200ms ease-out,
transform 220ms ease-out;
}
// Функция, которая вставляет элемент с enter-анимацией
function createAnimatedCard() {
const container = document.getElementById('container');
const card = document.createElement('div');
card.classList.add('card', 'card-enter');
card.textContent = 'Новая карточка';
container.appendChild(card);
// Форсируем reflow, чтобы браузер зафиксировал начальные стили
// Это важно, иначе переход может не сработать
void card.offsetWidth;
// Добавляем активный класс - анимация запускается
card.classList.add('card-enter-active');
// По окончании анимации можно убрать служебный класс card-enter
card.addEventListener('transitionend', () => {
card.classList.remove('card-enter');
}, { once: true });
}
// Здесь мы вызываем функцию, чтобы увидеть анимацию
createAnimatedCard();
Обратите внимание: строка с void card.offsetWidth; нужна для принудительного reflow — без этого браузер иногда «сливает» два состояния, и анимация не воспроизводится.
Enter-анимации через @keyframes
Вместо пара классов enter / enter-active можно использовать keyframes и animation. Это удобнее, если:
- вам нужно больше контроля над промежуточными состояниями;
- вы не хотите возиться с reflow в JS.
Пример:
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Этот класс сам по себе запускает анимацию появления */
.animate-fade-in-up {
animation: fadeInUp 220ms ease-out forwards;
/* forwards - сохраняем конечное состояние после анимации */
}
// Здесь мы создаем элемент и сразу даем ему класс с анимацией
function createAnimatedCardWithKeyframes() {
const container = document.getElementById('container');
const card = document.createElement('div');
card.classList.add('card', 'animate-fade-in-up');
card.textContent = 'Карточка с keyframes-анимацией';
container.appendChild(card);
}
При таком подходе вам не нужно отслеживать reflow, но иногда все равно полезно обрабатывать событие animationend, если нужно выполнить действие после завершения анимации.
Enter-animations в JavaScript-компонентах и фреймворках
Общий принцип для компонентных библиотек
В большинстве современных фреймворков (React, Vue, Angular, Svelte) принцип примерно одинаков:
- компонент управляет состоянием «смонтирован / не смонтирован»;
- вы задаете стили для enter-from и enter-to состояний;
- фреймворк автоматически добавляет и убирает нужные классы (или inline-стили) в нужные моменты жизненного цикла.
Смотрите, я кратко покажу на примере Vue и React, чтобы вы увидели общий механизм.
Пример: enter-animations во Vue (Transition)
Во Vue есть встроенный компонент Transition, который автоматически вешает классы:
- v-enter-from;
- v-enter-active;
- v-enter-to.
<template>
<!-- v-if управляет монтированием, Transition - анимацией появления -->
<button @click="isOpen = !isOpen">
Переключить панель
</button>
<Transition name="fade-slide">
<div v-if="isOpen" class="panel">
Содержимое панели
</div>
</Transition>
</template>
.panel {
padding: 16px;
background: #fff;
border-radius: 8px;
}
/* Начальное состояние (enter-from) */
.fade-slide-enter-from {
opacity: 0;
transform: translateY(8px);
}
/* Активное состояние (enter-active) */
.fade-slide-enter-active {
transition:
opacity 220ms ease-out,
transform 240ms ease-out;
}
/* Конечное состояние (enter-to) */
.fade-slide-enter-to {
opacity: 1;
transform: translateY(0);
}
Vue сам:
- добавляет v-if элемент в DOM;
- навешивает .fade-slide-enter-from и .fade-slide-enter-active;
- на следующий тик заменяет enter-from на enter-to;
- после завершения анимации убирает временные классы.
Вам остается только описать начальные и конечные состояния.
Пример: enter-animations в React (CSSTransition)
В React эта логика обычно выносится в сторонние библиотеки, например react-transition-group.
import { CSSTransition } from 'react-transition-group';
import { useState } from 'react';
function Panel() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen((prev) => !prev)}>
Переключить панель
</button>
<CSSTransition
in={isOpen} // Управляет появлением / исчезновением
timeout={220} // Длительность анимации
classNames="fade-slide" // Префикс для классов
unmountOnExit // Удалять из DOM после exit-анимации
>
<div className="panel">
Содержимое панели
</div>
</CSSTransition>
</>
);
}
.panel {
padding: 16px;
background: #fff;
border-radius: 8px;
}
/* Начальное состояние появления */
.fade-slide-enter {
opacity: 0;
transform: translateY(8px);
}
/* Активная фаза появления */
.fade-slide-enter-active {
opacity: 1;
transform: translateY(0);
transition:
opacity 220ms ease-out,
transform 240ms ease-out;
}
React Transition Group сам:
- монтирует компонент, когда in становится true;
- добавляет / убирает классы .fade-slide-enter и .fade-slide-enter-active;
- синхронизирует это с вашим timeout.
Здесь вы видите тот же паттерн: от начального состояния к конечному через transition.
Enter-animations и скролл (появление при прокрутке)
Многие интерфейсы используют анимации появления при скролле: элементы «проявляются» по мере того, как пользователь их видит. Тут ключевой вопрос — как определить момент входа элемента в видимую область.
Intersection Observer для запуска enter-анимаций
Лучший современный способ — Intersection Observer. Смотрите, я покажу минимально необходимый пример.
.section-block {
opacity: 0;
transform: translateY(20px);
transition:
opacity 260ms ease-out,
transform 280ms ease-out;
}
/* Класс, который переводит элемент в видимое состояние */
.section-block_visible {
opacity: 1;
transform: translateY(0);
}
// Настраиваем наблюдатель за пересечением с областью видимости
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const el = entry.target;
// Добавляем класс, который запускает переход к видимому состоянию
el.classList.add('section-block_visible');
// Больше не наблюдаем за этим элементом - enter-анимация нужна один раз
obs.unobserve(el);
}
});
},
{
threshold: 0.15, // Срабатывание при видимости ~15 процента элемента
}
);
// Находим все элементы, которым нужна анимация
document.querySelectorAll('.section-block').forEach((el) => observer.observe(el));
Пояснения:
- Изначально элементы скрыты (opacity 0, смещены).
- Когда часть элемента попадает в viewport (threshold 0.15), мы добавляем класс с конечным состоянием.
- Transition в CSS делает enter-анимацию.
Так можно реализовать аккуратные появления секций лендинга, карточек в сетке и т. д.
Производительность enter-animations
Принцип: анимировать только transform и opacity
Для плавных анимаций без потери FPS важно:
- избегать анимации свойств, которые вызывают пересчет layout (width, height, margin, top, left);
- по возможности ограничиваться transform и opacity.
То есть вместо:
.bad {
transition: top 200ms ease-out;
}
лучше сделать:
.good {
will-change: transform; /* Подсказываем браузеру, что будем анимировать transform */
transition: transform 200ms ease-out;
}
и использовать translateY / translateX, а не top / left.
Желательно избегать:
- анимаций больших теней с размытием;
- сложных фильтров (blur, drop-shadow), особенно на больших областях;
- масштабирования крупных контейнеров целиком (особенно со сложным содержимым).
Если вам нужно плавное «вылезание» большого экрана (например, мобильного меню), лучше анимировать обертку с transform: translateX, а не изменять ширину или позицию каждого внутреннего блока отдельно.
UX-паттерны и согласованность enter-animations
Согласованность по приложению
Хорошая практика — выработать набор стандартных enter-паттернов:
- базовое появление для карточек;
- появление модальных окон;
- появление уведомлений;
- переходы между экранами.
И затем придерживаться этих паттернов во всем приложении.
Например:
- все модалки появляются с zoom-in + fade 220 мс;
- все карточки списков — fade + slide-up 200 мс;
- тосты — slide-in справа 200 мс.
Так пользователю легче предсказывать поведение интерфейса.
Приоритет: не мешать взаимодействию
Enter-анимация не должна:
- блокировать нажатия дольше, чем это оправдано логикой продукта;
- заставлять ждать появления критически важного контента.
Если элемент важен для продолжения сценария (например, форма после нажатия кнопки), анимация должна быть короткой и не мешать началу ввода.
Появление без «мигания»
Иногда вы можете столкнуться с неприятным эффектом:
- элемент сначала мелькает в конечном состоянии;
- затем как будто «откатывается» в начальное и снова появляется.
Обычно это сигнал о том, что:
- вы слишком поздно применяете класс с начальными стилями;
- или разметка рендерится уже в конечном состоянии, а enter-информация приходит позже (например, в SPA-приложениях).
Решение: убедиться, что начальные стили применяются сразу при первом рендере. Во фреймворках это часто достигается через css-классы, завязанные на состояние монтирования (mounted, initialRender и т. д.).
Инструменты и подходы к организации кода анимаций
Выделение утилитарных классов
Удобно вынести часто используемые enter-animations в утилитарные классы, чтобы повторно использовать их в разных компонентах.
/* Базовые утилиты для появления */
.u-enter-fade {
opacity: 0;
transform: translateY(4px);
transition:
opacity 180ms ease-out,
transform 200ms ease-out;
}
.u-enter-fade_visible {
opacity: 1;
transform: translateY(0);
}
// Теперь вы можете в любом месте добавить элемент с .u-enter-fade,
// а затем в нужный момент добавить .u-enter-fade_visible
Это уменьшает дублирование и делает визуальную систему более предсказуемой.
CSS-переменные для настройки длительности и смещений
Если в проекте есть дизайн-система, удобно вынести основные параметры в CSS-переменные.
:root {
--anim-duration-fast: 160ms;
--anim-duration-normal: 220ms;
--anim-translate-small: 6px;
}
.fade-enter {
opacity: 0;
transform: translateY(var(--anim-translate-small));
}
.fade-enter-active {
opacity: 1;
transform: translateY(0);
transition:
opacity var(--anim-duration-normal) ease-out,
transform var(--anim-duration-normal) ease-out;
}
Так вы сможете централизованно менять feel всех анимаций, не проходя по десяткам файлов.
Доступность и настройки предпочтений пользователя
Уважение prefers-reduced-motion
Некоторым пользователям анимации мешают или даже вызывают дискомфорт. Современные браузеры предоставляют медиа-запрос prefers-reduced-motion.
Смотрите, как можно адаптировать enter-animations:
.modal-enter {
opacity: 0;
transform: translateY(10px);
}
.modal-enter-active {
opacity: 1;
transform: translateY(0);
transition:
opacity 220ms ease-out,
transform 260ms ease-out;
}
/* Если пользователь предпочитает минимум движения */
@media (prefers-reduced-motion: reduce) {
.modal-enter {
opacity: 0;
transform: none; /* Убираем движение */
}
.modal-enter-active {
opacity: 1;
transform: none;
transition: opacity 120ms linear; /* Быстрое простое появление */
}
}
Так вы:
- сохраняете сам факт появления (контент по-прежнему не «прыгает»);
- уменьшаете сложность и длительность движения.
Пропуск сложных анимаций при слабой производительности
Иногда есть смысл:
- отключать сложные enter-animations на старых устройствах;
- упрощать их в low-power режиме или при ухудшении FPS.
Проверки могут быть привязаны к:
- типу устройства (mobile / desktop);
- некоторым эвристикам (например, количество dropped frames);
- кастомной настройке пользователя внутри вашего приложения.
Заключение
Enter-animations — это не просто эффект «для красоты». Они помогают объяснять интерфейс: показывать, откуда появляются элементы и как они связаны между собой. Чтобы анимации появления работали на вас, а не против, важно:
- мыслить в терминах состояний: начальное, активное, конечное;
- выбирать простой и понятный паттерн под каждую задачу (fade, slide, scale);
- опираться на разумные значения длительностей и тайминг-функций;
- учитывать производительность и доступность (prefers-reduced-motion);
- выстраивать единый язык анимаций в рамках всего продукта.
Смотрите, вы теперь можете:
- реализовать базовые enter-animations на чистом CSS;
- запускать анимации при динамическом добавлении элементов через JavaScript;
- использовать возможности фреймворков для управления появлением компонентов;
- организовать код анимаций так, чтобы им было легко управлять и поддерживать.
Частозадаваемые технические вопросы
1. Как сделать enter-анимацию только при первом появлении компонента, но не при последующих показах
Часто нужно анимировать элемент только при первом монтировании, а при повторных открытиях показывать его сразу.
Подход:
- Храните флаг в состоянии (например, hasEntered).
- При первом монтировании запускайте enter-анимацию.
- После завершения анимации выставляйте флаг hasEntered в true.
- При следующих открытиях применяйте уже конечные стили без анимации.
В React это может быть состояние в родителе, в Vue — реактивное свойство в компоненте. Логика одна и та же — анимация завязана не только на isOpen, но и на hasEntered.
2. Как синхронизировать enter и exit-анимации для нескольких элементов, чтобы они шли каскадом
Если нужно, чтобы элементы появлялись или исчезали «лесенкой», полезно использовать задержку на основе индекса элемента.
Например:
- добавляйте атрибут data-index к элементам;
- в JS вычисляйте задержку как index * baseDelay;
- добавляйте inline-стиль transition-delay или animation-delay для каждого элемента.
Так вы получите управляемый каскад, который легко настраивается одной константой baseDelay.
3. Что делать, если enter-анимация дергается из-за изменения размеров контейнера
Иногда при появлении элемента пересчитывается layout, и анимация выглядит дерганой. Чтобы это уменьшить:
- по возможности резервируйте место заранее (фиксированная высота/ширина, skeleton);
- анимируйте внутренний контент, а не сам контейнер, несущий layout;
- избегайте одновременной анимации размеров и позиционирования множества соседних элементов.
Если нужно анимировать вставку в список, обратите внимание на FLIP-подход (First, Last, Invert, Play).
4. Как отменить enter-анимацию, если элемент должен исчезнуть еще до ее окончания
Бывает, что пользователь быстро закрывает элемент (например, модалку), пока она еще «въезжает». Чтобы избежать зависаний:
- при старте exit-анимации снимайте классы enter и enter-active;
- отменяйте запланированные таймеры или слушатели transitionend, связанные с enter;
- следите за тем, чтобы enter и exit не пытались управлять одними и теми же свойствами одновременно в разные стороны.
Лучше всего централизовать управление состояниями анимации: элемент в каждый момент должен быть либо в режиме enter, либо exit, либо стабильный.
5. Как тестировать enter-animations автоматическими тестами
Автотесты плохо работают с анимациями, если не управлять временем. Решения:
- в тестовой среде отключать анимации (например, подменять CSS на duration 0);
- в e2e-тестах дожидаться не по времени, а по состоянию (ждать появления итоговых классов или атрибутов);
- для unit-тестов логики анимации тестировать не сам визуальный эффект, а изменение состояний (флагов, классов), которые должны приводить к нужным анимациям.
Постройте личный план изучения Vue до уровня Middle — бесплатно!
Vue — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Vue
Лучшие курсы по теме

Vue 3 и Pinia
Антон Ларичев
TypeScript с нуля
Антон Ларичев