Сегодня покажу, как выкинуть JS из связки «скролл + эффекты». Речь про CSS scroll-driven animations — это уже не polyfill, а нативный API в Chrome/Edge/Safari 26+. Два главных героя: scroll() и view(). Поехали.
**Основы**
scroll() привязывает анимацию к прогрессу прокрутки контейнера. Элемент просто крутится/двигается/закрашивается в зависимости от того, сколько пикселей мы проскроллили.
view() привязывает анимацию к появлению элемента во вьюпорте. Элемент «оживает», когда вползает в кадр, и «умирает», когда выходит. И это идеально для параллакса или плавного появления карточек.
Оба механизма работают через animation-timeline, а за точками старта и конца управляет animation-range.
**Прогресс-бар без единой строчки JS**
Классика: полоска сверху показывает, сколько прочитано. Раньше — слушатель скролла и requestAnimationFrame. Теперь — 5 строк CSS:
.progress {
position: fixed;
top: 0; left: 0;
width: 100%; height: 4px;
background: #ff4d00;
transform-origin: left;
animation: scale linear;
animation-timeline: scroll();
}
@keyframes scale {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
Всё. scroll() без аргументов = отслеживаем скролл ближайшего предка (или корня, если предка-скроллера нет). Анимация идёт ровно от верха страницы до низа.
**Параллакс через view()**
Берём картинку, которая должна двигаться с другой скоростью, пока она на экране. Вместо scroll-слушателя и пересчёта позиций:
.parallax-image {
animation: drift linear;
animation-timeline: view();
animation-range: entry 0% cover 40%;
}
@keyframes drift {
from { transform: translateY(15%); }
to { transform: translateY(-15%); }
}
Поясню значения entry, cover, exit. Палитра простая:
- entry — элемент выезжает снизу/сверху в кадр
- cover — полностью на экране
- exit — покидает вьюпорт
entry 0% cover 40% значит: анимация начинается в момент появления и заканчивается, когда элемент прошёл 40% высоты экрана. За счёт этого картинка «сдвигается» независимо от скорости скролла.
**Важный нюанс: animation-range**
Если у вас контейнер высотой 200%, а анимация должна идти только на середине скролла — играйтесь именно через range. scroll(root) + range: 0% 50% даст эффект, заканчивающийся на половине страницы. Это заменяет кучу логики «если y > X».
**Бонус-фича: animation-timeline: view(block)**
По умолчанию отслеживается ось блока (вертикаль) или инлайна (горизонталь). Можно явно указать view(inline) для горизонтальных слайдеров-каруселей с прокруткой по X. Тоже без JS.
**Что под капотом и поддержка**
Работает только с ключевыми кадрами — CSS-переходы так не умеют. animation-timeline можно комбинировать с обычной animation-duration, но при наличии timeline длительность игнорируется: прогрессом правит скролл.
Поддержка в 2025-м: Chrome 115+, Edge 115+, Safari 26+ (с префиксом -webkit- в iOS 26). Firefox — за флагом, но уже в активной разработке. Для отстающих браузеров это деградирует безболезненно: без поддержки просто не будет анимации.
**Моя рекомендация**
Не пишите свой параллакс через JS, если это обычный маркетинговый лендинг. Нативный scroll-driven API считается на стороне compositor, поэтому дёрганий и микролагов на слабых устройствах меньше, чем при ручном обновлении свойств из JS.
Вывод: добавьте scroll() на прогресс-бар, view() на карточки — почувствуете разницу в скорости вёрстки сразу.