Анимация при прокрутке
Обычная анимация идёт по часам: две секунды — и конец. Но её ход можно привязать не ко времени, а к прокрутке: прокрутили страницу наполовину — анимация прошла наполовину, вернулись назад — она отмоталась обратно. Раньше такое писали на JavaScript, теперь хватает одного свойства CSS.
Шкала прокрутки: scroll()
Свойство animation-timeline задаёт шкалу — то, что двигает анимацию. Функция scroll() связывает её с прокруткой ближайшего прокручиваемого блока. Сделаем индикатор чтения — полосу, которая растёт по мере прокрутки текста:
<style>
@keyframes grow {
from { scale: 0 1; }
to { scale: 1 1; }
}
.reader { height: 180px; overflow-y: auto; border: 1px solid silver; }
.progress {
position: sticky;
top: 0;
height: 6px;
background: tomato;
transform-origin: left;
animation: grow linear;
animation-timeline: scroll();
}
.reader p { margin: 12px; }
</style>
<div class="reader">
<div class="progress"></div>
<p>Прокручивайте этот текст — красная полоса сверху показывает, сколько уже прочитано.</p>
<p>Эспрессо готовят под давлением: горячая вода за полминуты проходит через плотно спрессованный молотый кофе.</p>
<p>Капучино — это эспрессо, молоко и густая молочная пена примерно в равных долях.</p>
<p>В латте молока больше, а пена тонкая, поэтому вкус мягче.</p>
<p>Флэт уайт делают на двойном эспрессо с тонким слоем бархатистого молока.</p>
<p>Вы дочитали до конца — полоса заполнилась целиком.</p>
</div>Длительность не указана: при шкале прокрутки её роль играет путь от начала до конца прокрутки. На своей странице ставьте полосе position: fixed — тогда scroll() найдёт прокрутку всей страницы.
Шкала видимости: view()
Функция view() следит не за всей прокруткой, а за тем, как сам элемент проходит через видимую область. Так делают появление блоков при прокрутке:
<style>
@keyframes reveal {
from { opacity: 0; translate: -60px 0; }
to { opacity: 1; translate: 0 0; }
}
.list { height: 200px; overflow-y: auto; padding: 8px; border: 1px solid silver; }
.item {
margin-bottom: 12px;
padding: 24px 12px;
background: lavender;
animation: reveal linear both;
animation-timeline: view();
animation-range: entry;
}
</style>
<div class="list">
<div class="item">Капучино</div>
<div class="item">Латте</div>
<div class="item">Раф</div>
<div class="item">Флэт уайт</div>
<div class="item">Эспрессо</div>
<div class="item">Американо</div>
</div>Каждая карточка выезжает слева, пока появляется снизу. Прокрутите назад — анимация отыграет обратно. both в animation оставляет первый кадр до начала и последний после конца.
Отрезок: animation-range
animation-range выбирает, на каком отрезке пути элемента идёт анимация. В примере выше entry — пока карточка въезжает в область видимости:
| Отрезок | Когда идёт анимация |
|---|---|
cover | всё время, пока элемент хоть немного виден (по умолчанию) |
contain | пока элемент виден целиком |
entry | пока элемент въезжает в область видимости |
exit | пока элемент уезжает из неё |
К отрезку можно добавить проценты: entry 20% cover 50% — от 20% въезда до середины пути.
Поддержка и запасной вариант
Анимации при прокрутке работают в Chrome и Edge с 2023 года, позже их добавил Safari; актуальную поддержку, особенно в Firefox, смотрите на caniuse.com. Если элементы появляются из прозрачности, в старом браузере они могут остаться невидимыми. Поэтому такие стили оборачивают в @supports:
@supports (animation-timeline: view()) {
.item {
animation: reveal linear both;
animation-timeline: view();
animation-range: entry;
}
}Не забывайте и о prefers-reduced-motion из урока «Анимации»: людям, которые отключили движение, достаточно просто показать содержимое.
Частая ошибка
animation-timeline перед animation. Сокращённое свойство animation сбрасывает все свои составляющие, в том числе шкалу, обратно на время. Анимация длительностью 0 заканчивается мгновенно: полоса сразу заполнена целиком и не двигается:
<style>
@keyframes grow { from { scale: 0 1; } to { scale: 1 1; } }
.reader { height: 120px; overflow-y: auto; border: 1px solid silver; }
.bar { position: sticky; top: 0; height: 6px; background: crimson; transform-origin: left;
animation-timeline: scroll();
animation: grow linear; }
.reader p { margin: 12px; height: 200px; }
</style>
<div class="reader">
<div class="bar"></div>
<p>Прокручивайте — полоса не меняется.</p>
</div>Сначала пишите animation, а animation-timeline и animation-range — после него.