Чтобы блок или список плавно "выезжал" на экран при открытии страницы, элемент стартует с opacity:0 и небольшим смещением, а animation с fill-mode:forwards доводит его до обычного вида и оставляет так. Для списка добавляется animation-delay на каждый элемент - но без fill-mode:backwards он перед стартом мигнёт, а если элемент вообще ниже экрана, анимация закончится ещё до того, как её кто-то увидит.
В этом уроке
Попробуй прямо сейчас: обнови блокнот (или просто подожди - код перерисовывается сам при любом изменении) и три пункта появятся не одновременно, а друг за другом, с нарастающей задержкой в style="animation-delay". Убери строку animation-fill-mode: backwards из .item - и пункты начнут коротко мигать полностью видимыми перед тем, как исчезнуть и заново проявиться, вместо чистого появления из невидимого состояния.
Один блок: opacity, translateY и fill-mode: forwards
Базовый паттерн опирается на уже пройденную механику: @keyframes с явными 0% и 100% задаёт начальное невидимое состояние (opacity:0 и небольшой сдвиг вниз через translateY) и конечное обычное состояние, а animation-fill-mode: forwards оставляет элемент в конечном состоянии после того, как анимация один раз проиграла - без forwards элемент откатился бы обратно к opacity:0 и просто исчез бы сразу после "появления".
.fade-in {
opacity: 0;
animation: fadeInUp .6s ease-out forwards;
}
@keyframes fadeInUp {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
} - opacity:0 в базовом классе подстраховывает на случай, если анимация ещё не подключилась (например, при медленной загрузке CSS).
.fade-in {
animation: fadeInUp .6s ease-out;
}
@keyframes fadeInUp {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
} - fill-mode не указан (по умолчанию none): сразу после конца анимации элемент откатится к исходному opacity, заданному ВНЕ анимации (обычно 1), но конечный 100%-кадр применялся лишь мгновение - в большинстве браузеров визуально это не страшно, однако правило хрупкое и зависит от того, что ничего больше не переопределяет opacity.
Список по очереди: animation-delay и fill-mode: backwards
Чтобы элементы списка появлялись не разом, а друг за другом, у каждого элемента задаётся свой animation-delay - обычно через :nth-child() или инлайновую переменную, растущий с шагом 0.1-0.2s. Но здесь есть подводный камень: animation-delay - это пауза ПЕРЕД стартом анимации, и всё это время элемент по умолчанию виден в своём обычном состоянии, а не в состоянии первого кадра анимации. Из-за этого весь список сначала мелькает полностью видимым, а затем каждый пункт резко "пропадает" и заново плавно проявляется - вместо чистого появления из ничего.
Решение то же самое, что уже разбирали в уроке про animation-fill-mode: значение backwards заставляет элемент выглядеть как на 0%-кадре анимации ещё ДО её реального старта, то есть всю паузу animation-delay элемент остаётся невидимым, а не мелькает. Для списка это обязательная привычка, а не необязательная деталь.
.item:nth-child(1) { animation-delay: .1s; }
.item:nth-child(2) { animation-delay: .3s; }
.item:nth-child(3) { animation-delay: .5s; }
.item {
animation: fadeInUp .5s ease-out forwards;
animation-fill-mode: backwards;
} - во время своей задержки каждый пункт невидим, мигания нет.
.item:nth-child(1) { animation-delay: .1s; }
.item:nth-child(2) { animation-delay: .3s; }
.item {
animation: fadeInUp .5s ease-out forwards;
} - fill-mode: backwards не указан: все пункты на мгновение видны целиком, потом резко пропадают и только затем плавно появляются.
Отдельная граница применимости паттерна, которую важно понимать: таймер CSS-анимации при загрузке страницы стартует сразу, как только применились стили - независимо от того, виден ли элемент на экране. Если блок находится далеко от начала страницы (пользователь ещё не долистал), к моменту прокрутки до него однократная анимация уже давно закончится, и он просто появится готовым, без всякого эффекта. Настоящее появление "по мере прокрутки", как на некоторых крупных сайтах, чистым CSS не делается - для отслеживания момента попадания элемента в область видимости нужен JavaScript (обычно IntersectionObserver), это уже за рамками курса про чистый CSS.
Частые ошибки новичков
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| Не указывать animation-fill-mode: forwards для анимации появления | После конца анимации элемент откатывается к стилям вне анимации, а не остаётся в конечном виде | Всегда добавлять forwards к анимациям появления при загрузке |
| Задавать animation-delay для списка элементов без fill-mode: backwards | Элементы на мгновение видны целиком перед стартом, затем резко пропадают и заново появляются | Добавлять animation-fill-mode: backwards (или сокращённо both) к каждому элементу с задержкой |
| Вешать анимацию появления при загрузке на блок, который находится далеко ниже экрана | Анимация проигрывается и заканчивается, пока пользователь ещё не долистал до блока - эффекта никто не увидит | Для контента ниже экрана нужен JS с IntersectionObserver, а не анимация при загрузке страницы |
| Забывать про пользователей с включённым "меньше анимации" в настройках системы | Анимация появления может быть навязчивой для части посетителей независимо от их желания | Учитывать @media (prefers-reduced-motion: reduce) - подробнее в отдельном уроке курса про доступность |
Шпаргалка
| Запись | Что означает |
|---|---|
| opacity:0 + translateY(20px) в 0%-кадре | Начальное невидимое и сдвинутое состояние |
| animation-fill-mode: forwards | Элемент остаётся в конечном виде после конца анимации |
| animation-fill-mode: backwards | Элемент невидим уже во время animation-delay, до старта |
| .item:nth-child(N) { animation-delay: ... } | Каждый элемент списка стартует со своей задержкой - эффект "по очереди" |
| Элемент ниже экрана + анимация при загрузке | Анимация закончится до того, как пользователь долистает - нужен JS для скролла |
Коротко: отвечаем на вопросы
Почему анимация не проигрывается, когда я долистываю до блока внизу страницы?
Потому что таймер CSS-анимации, запущенной при загрузке, стартует сразу, как только применились стили, - независимо от того, виден элемент на экране или нет. Если блок находится далеко от начала страницы, однократная анимация обычно успевает полностью закончиться ещё до того, как пользователь до него долистает, и он появится сразу готовым, без всякого эффекта. Настоящее появление "при попадании в область видимости" чистым CSS не делается - для этого нужен JavaScript с IntersectionObserver, отслеживающим момент прокрутки до элемента.
Нужен ли JavaScript, чтобы анимация появления запустилась при загрузке страницы?
Нет - в отличие от hover-эффектов, которым нужно реальное действие пользователя, CSS-анимация с animation-name начинает играть автоматически, как только браузер применил стили к элементу, без единой строчки JavaScript. JS понадобится только для более сложного сценария - запуска анимации именно в момент прокрутки до конкретного блока, а не сразу при загрузке всей страницы.