Анимации на CSS scroll-driven — без библиотек

Раньше для анимации по скроллу тянули GSAP, Lenis, ScrollTrigger — 50–120 КБ JS. В 2026 нативный CSS `animation-timeline: scroll()` и `view()` закрывают 80% задач без единой строки JS. Разбираем синтаксис, поддержку и реальные кейсы.

Скролл-анимации — отдельный мир: progress bar сверху, параллакс героев, ленивое появление карточек, sticky-секции с метаморфозами. До 2024 это значило тянуть GSAP + ScrollTrigger, Locomotive, Lenis, Framer Motion — от 40 до 150 КБ минифицированного JS, плюс прогрев main thread на каждый кадр.

В Chrome 115+, Edge 115+ и за Safari TP/Firefox 129+ работает CSS scroll-driven animations — нативная связка анимации с прогрессом скролла или видимостью элемента. На лендингах студии в 2026 это покрывает 80% сценариев без библиотек.

Что появилось

Два новых типа таймлайна для CSS-анимаций:

  • animation-timeline: scroll() — прогресс целого скролл-контейнера (страница или div с overflow).
  • animation-timeline: view() — прогресс пересечения элемента с viewport, как IntersectionObserver, только в CSS.

Оба работают с обычным @keyframes. Никакого JS, никаких rAF-циклов, всё на композиторе.

Кейс 1: progress bar сверху

Классика — полоса прогресса чтения статьи.

.progress {
  position: fixed;
  top: 0; left: 0;
  height: 3px;
  background: #e63946;
  transform-origin: 0 0;
  animation: progress linear;
  animation-timeline: scroll(root);
}
@keyframes progress {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

Семь строк. До этого — подписка на scroll, debounce, расчёт scrollHeight - clientHeight, requestAnimationFrame. Здесь — браузер сам.

Кейс 2: появление карточек при попадании в экран

view() привязывает прогресс анимации к пересечению элемента с viewport. Можно анимировать opacity и transform без IntersectionObserver.

.card {
  opacity: 0;
  transform: translateY(40px);
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 50%;
}
@keyframes reveal {
  to { opacity: 1; transform: none; }
}

animation-range: entry 0% entry 50% читается так: анимация идёт от момента, когда элемент только начал входить в viewport (0%), до момента, когда он зашёл на 50% высоты viewport.

Ключевые значения animation-range: cover (вся видимая фаза), contain (фаза, когда элемент полностью внутри), entry (вход), exit (выход).

Кейс 3: параллакс героя

.hero-bg {
  animation: parallax linear;
  animation-timeline: scroll(root);
  animation-range: 0 100vh;
}
@keyframes parallax {
  to { transform: translateY(-20%); }
}

Фон героя поднимается на 20% при первом экране скролла. Никаких rAF, никаких will-change подкручиваний.

Кейс 4: sticky-секция с морфингом

Самое сильное — пин-секции, где при скролле меняется состояние элемента: иконка превращается в логотип, цифра растёт, текст переезжает.

.stage {
  position: sticky;
  top: 0;
  height: 100vh;
}
.stage h2 {
  animation: stage-title linear;
  animation-timeline: view();
  animation-range: cover;
}
@keyframes stage-title {
  0%   { font-size: 1rem; opacity: 0.4; }
  50%  { font-size: 5rem; opacity: 1; }
  100% { font-size: 1.5rem; opacity: 0.8; }
}

Поддержка в 2026

На июнь 2026:

  • Chrome, Edge, Opera — стабильно с 115 (август 2023).
  • Safari — 17.4+ (март 2024) частично, 18+ полноценно.
  • Firefox — 129+ (август 2024) с флагом, 130+ по умолчанию.

Глобальный охват по caniuse — ~92%. Для landing студии этого достаточно. Для остального — graceful degradation: пользователь без поддержки видит финальное состояние без анимации.

Прогрессивное улучшение

Простой паттерн — feature query:

.card { opacity: 1; transform: none; }
@supports (animation-timeline: view()) {
  .card {
    opacity: 0;
    transform: translateY(40px);
    animation: reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 50%;
  }
}

Старый Safari — карточки сразу видны. Современный — плавное появление.

Когда брать JS-библиотеку

CSS scroll-driven не закрывают всё. JS остаётся нужен, когда:

  • Нужно smooth scrolling (Lenis) — CSS не сглаживает сам трэкпад/колесо.
  • Анимация по нескольким триггерам сразу (скролл + курсор + время).
  • Sequence с зависимостями между несколькими элементами по timeline.
  • Pin-секции с complex ease (overshoot, bounce между точками).
  • Нужна поддержка Safari 16 и старше — там полифилл огромный.

Что прячут библиотеки

GSAP ScrollTrigger делает три вещи, которые CSS пока не умеет:

  1. Координация нескольких анимаций по одному progress.
  2. Колбэки на достижение точек (для аналитики, ленивой загрузки).
  3. Reverse-on-leave с инерцией.

Если задача — «логотип сделай помельче когда скроллит и плавно увеличь когда вернулся вверх», CSS справится через два animation-range. Если задача — «при достижении 30% запусти видео и подсветь третью карточку», нужен JS.

Перформанс

CSS scroll-driven работают на композиторе. Это значит: 60 FPS даже на бюджетных Android 2020 года, нулевая нагрузка на main thread, нет jank от React-перерисовок. Lighthouse не штрафует.

Сравните с типичным GSAP-параллаксом: каждый кадр JS пересчитывает offset, ставит inline-стиль, браузер перерисовывает. На странице из 20 карточек с reveal — гарантированный лаг на старых устройствах.

Грабли, на которые наступают

  • Забыли both. Без animation-fill-mode: both элемент дёргается в начальное состояние при выходе из range.
  • Перепутали scroll() и view(). scroll() — прогресс контейнера, view() — прогресс конкретного элемента. Параллакс героя — scroll(root), появление карточки — view().
  • Range в неправильных единицах. entry 0% entry 50% — проценты от высоты viewport, не от размера элемента.
  • Анимация transform + opacity на <img>. Браузер не всегда композитит — иногда падает на main thread. Делайте transform на родителе.

Вывод

В 2026 нативные CSS scroll-driven закрывают reveal, progress bar, параллакс и sticky-морфинг без единой строки JS. GSAP остаётся только под сложные сценарии с координацией и колбэками. Для лендинга студии это означает минус 80–120 КБ JS, 60 FPS на любых устройствах и нулевую боль с lifecycle компонентов.