Библиотека Motion One предоставляет низкоуровневый и при этом выразительный API для анимаций в браузере, где ключевым строительным блоком выступает реактивность к прокрутке страницы. Работа со скроллом в Motion One строится вокруг концепции привязки анимации к положению документа или элемента относительно viewport, что позволяет реализовывать как простые эффекты появления, так и сложные сценические переходы с синхронизацией движения.
Основной инструмент для работы с прокруткой — функция
scroll, которая связывает прогресс прокрутки с изменением
свойств анимации. В отличие от классических событий scroll,
где требуется вручную вычислять позиции элементов, Motion One
абстрагирует расчёты и предоставляет нормализованное значение
прогресса.
В основе механизма лежит преобразование скролла в числовую шкалу. Эта шкала используется как управляющий параметр для анимации.
p =
Где p — нормализованный прогресс, ограниченный
диапазоном от 0 до 1. Этот подход позволяет унифицировать поведение
анимаций вне зависимости от размеров экрана или контента.
Функция scroll в Motion One принимает коллбэк, который
получает обновления этого прогресса:
import { scroll, animate } from "motion";
scroll(({ y }) => {
// y прогресс прокрутки по вертикали (0 → 1)
});
Значение y отражает относительное положение прокрутки
страницы. Аналогично доступно горизонтальное направление x,
если используется горизонтальный контейнер.
Одним из базовых сценариев является прямое связывание прогресса прокрутки с CSS-свойствами элемента.
scroll(({ y }) => {
animate(".box", {
opacity: y,
transform: `translateY(${(1 - y) * 50}px)`
});
});
В данном случае анимация полностью зависит от положения страницы: элемент становится прозрачнее и смещается по мере прокрутки.
Такой подход подходит для простых эффектов, однако Motion One позволяет более тонко управлять диапазонами активации.
Скролл-анимации редко должны зависеть от всей страницы. Чаще требуется запуск в пределах конкретного сегмента документа. Для этого используются offsets — параметры, задающие границы начала и конца анимации.
Типичная модель выглядит как интервал [start, end], где
значения могут быть заданы в процентах, пикселях или относительных
единицах.
scroll(
({ y }) => {
animate(".panel", {
scale: y
});
},
{
offset: ["start end", "end start"]
}
);
Такая конфигурация задаёт момент начала анимации при появлении элемента внизу viewport и завершение при его полном выходе вверх.
Scroll triggers в Motion One часто реализуются через сочетание
прогресса и порогов активации. Это позволяет имитировать поведение
IntersectionObserver, но с более плавной и управляемой
интерполяцией.
Где t — порог срабатывания. Однако в Motion One вместо
дискретного переключения используется интерполяция, что делает переходы
визуально мягкими.
Одним из сильных аспектов подхода Motion One является возможность одновременно управлять несколькими CSS-свойствами через один scroll-источник.
scroll(({ y }) => {
animate(".card", {
opacity: y,
transform: `
translateY(${(1 - y) * 40}px)
rotate(${(1 - y) * 5}deg)
`
});
});
Такой подход создаёт многомерное движение, где один параметр прокрутки управляет целым набором трансформаций.
При усложнении логики анимации используется концепция временной шкалы, где скролл становится заменой времени. В этом режиме прокрутка выступает как аналог временной оси.
t_{animation} = f(p_{scroll})
Функция f определяет нелинейное преобразование, например
easing, ускорение или задержку.
scroll(({ y }) => {
const eased = y * y;
animate(".scene", {
transform: `translateX(${eased * 200}px)`
});
});
Использование нелинейных функций позволяет имитировать физические модели движения, включая инерцию и ускорение.
Сложные интерфейсы часто состоят из нескольких независимых зон скролла. Motion One поддерживает локальные контейнеры, где прокрутка отслеживается относительно элемента, а не всего документа.
scroll(
({ y }) => {
animate(".section", {
scale: y
});
},
{
target: document.querySelector(".container")
}
);
В таком случае прогресс вычисляется внутри заданного контейнера, что позволяет создавать изолированные анимационные сцены.
Scroll triggers часто комбинируются с логикой появления элемента в viewport. Это создаёт гибридную модель: элемент не только отслеживает прокрутку, но и активируется только при достижении области видимости.
scroll(({ y }) => {
const element = document.querySelector(".feature");
if (y > 0.2 && y < 0.8) {
animate(element, {
opacity: 1,
transform: "translateY(0px)"
});
} else {
animate(element, {
opacity: 0,
transform: "translateY(20px)"
});
}
});
Такой подход позволяет формировать сегментированное поведение интерфейса, где каждая зона страницы отвечает за собственное состояние.
Параллакс-эффекты — один из наиболее распространённых сценариев scroll triggers. Motion One позволяет создавать многослойное движение, где каждый слой имеет собственную скорость относительно прогресса.
scroll(({ y }) => {
animate(".background", {
transform: `translateY(${y * 100}px)`
});
animate(".foreground", {
transform: `translateY(${y * 30}px)`
});
});
Разница коэффициентов создаёт иллюзию глубины пространства.
Ключевая особенность Motion One заключается в оптимизации обновлений.
Вместо прямого реагирования на каждый scroll-event
используется батчинг и синхронизация с
requestAnimationFrame. Это уменьшает количество
перерасчётов layout и повышает плавность анимации.
Поток обработки можно представить следующим образом:
scrollEvent normalize(p) frameUpdate animate()
Такая цепочка обеспечивает стабильное поведение даже при высокой частоте прокрутки.
В более сложных интерфейсах scroll triggers часто дополняются проверкой пересечения элемента с viewport. Это позволяет запускать анимацию только при необходимости, избегая лишних вычислений.
import { scroll, animate } from "motion";
scroll(({ y }) => {
document.querySelectorAll(".item").forEach((item) => {
const rect = item.getBoundingClientRect();
const visible = rect.top < window.innerHeight && rect.bottom > 0;
if (visible) {
animate(item, {
opacity: y
});
}
});
});
Такой гибридный подход используется в интерфейсах с большим количеством элементов, где важно контролировать нагрузку.
Motion One позволяет использовать координаты элементов как основу для вычисления прогресса. Это даёт возможность создавать эффекты, где каждый элемент реагирует индивидуально на положение прокрутки.
scroll(({ y }) => {
const cards = document.querySelectorAll(".card");
cards.forEach((card, index) => {
animate(card, {
transform: `translateY(${y * (index * 10)}px)`
});
});
});
Так формируется каскадное движение, где элементы реагируют с разной интенсивностью.
Чтобы избежать линейной зависимости, применяются функции интерполяции. Они преобразуют линейный прогресс в нелинейный, приближая движение к естественной физике.
y’ = y^k, k > 1
При увеличении степени k движение становится более
замедленным в начале и ускоренным в конце, что часто используется для
эффектов появления.
scroll(({ y }) => {
const smooth = Math.pow(y, 1.8);
animate(".title", {
transform: `translateY(${(1 - smooth) * 60}px)`
});
});
Scroll triggers могут использоваться не только для визуальных эффектов, но и для управления логикой интерфейса. Прогресс прокрутки становится источником состояния.
scroll(({ y }) => {
const state = y > 0.5 ? "active" : "idle";
document.body.dataset.state = state;
});
Такой подход позволяет синхронизировать CSS и JavaScript без дополнительных событий.
В сложных интерфейсах часто присутствует несколько независимых
scroll-источников: основной документ, модальные окна, горизонтальные
секции. Motion One допускает параллельное использование нескольких
scroll-наблюдателей, каждый из которых управляет своей
областью анимации.
scroll(({ y }) => {
animate(".main", { opacity: y });
});
scroll(
({ y }) => {
animate(".modal", { scale: y });
},
{ target: ".modal-container" }
);
Такая композиция формирует многослойную систему триггеров, где каждый слой живёт в собственной координатной системе.
При программном изменении scroll-позиции (например, через
scrollTo) Motion One сохраняет непрерывность прогресса,
интерполируя переходы между значениями. Это предотвращает резкие
визуальные разрывы и поддерживает целостность анимационного
состояния.
Математически это можно представить как сглаживание сигнала:
p_{smoothed}(t) = p(t) + (1 - )p(t-1)
Где коэффициент сглаживания регулирует инерционность переходов.