Скроллинг представляет собой непрерывное изменение числового значения, которое естественным образом подходит для управления анимацией. В основе подхода лежит преобразование текущей позиции прокрутки в параметры движения интерфейса: смещение, прозрачность, масштаб, скорость, фазу интерполяции.
Библиотека Popmotion предоставляет низкоуровневые примитивы для работы с потоками значений, физическими моделями и интерполяцией, что позволяет строить сложные scroll-driven интерфейсы без привязки к фреймворку.
Любая логика начинается с получения значения прокрутки. В
веб-контексте это чаще всего window.scrollY, однако прямое
использование значения приводит к избыточным перерасчётам и усложнению
синхронизации анимаций.
Popmotion позволяет преобразовать поток событий в управляемый stream:
import { fromEvent, map } from 'popmotion';
const scrollY$ = fromEvent(window, 'scroll').pipe(
map(() => window.scrollY)
);
Здесь формируется реактивный поток, который можно связывать с анимационными свойствами.
Скролл почти никогда не используется напрямую. Требуется преобразование диапазонов: от пикселей страницы к нормализованным значениям.
import { transform } from 'popmotion';
const mapScroll = transform([
[0, 500],
[0, 1]
]);
Теперь любое значение прокрутки можно привести к диапазону
[0, 1], который удобно использовать для анимации opacity,
scale или transform.
Связывание scroll-значения с визуальным состоянием чаще всего
строится через onUpdate и ручное применение стилей.
scrollY$.subscribe((y) => {
const progress = mapScroll(y);
element.style.opacity = 1 - progress;
element.style.transform = `translateY(${progress * 100}px)`;
});
Такой подход формирует прямую зависимость между прокруткой и состоянием DOM-элемента.
Резкие изменения scroll создают визуальный шум. Для его устранения применяется сглаживание через spring-модель или инерционный фильтр.
Popmotion содержит физические модели, позволяющие имитировать естественное движение.
import { spring } from 'popmotion';
spring({
from: 0,
to: 1,
stiffness: 80,
damping: 20
}).start((v) => {
element.style.transform = `scale(${v})`;
});
При использовании со scroll-значениями spring часто применяется как промежуточный слой между текущим значением и визуальным состоянием.
Параллакс основан на различной скорости движения слоёв относительно прокрутки. Это достигается масштабированием scroll-значения с разными коэффициентами.
scrollY$.subscribe((y) => {
const backgroundOffset = y * 0.3;
const foregroundOffset = y * 0.8;
bg.style.transform = `translateY(${backgroundOffset}px)`;
fg.style.transform = `translateY(${foregroundOffset}px)`;
});
Popmotion позволяет структурировать такие зависимости через transform pipelines, избегая дублирования вычислений.
Скролл часто разбивается на логические сегменты, внутри которых поведение анимации отличается. Это реализуется через условные трансформации значений.
const sectionProgress = transform([
[0, 300, 0],
[300, 800, 1],
[800, 1200, 0.5]
]);
Каждый сегмент задаёт собственную интерполяцию, создавая сложные сценарии поведения интерфейса.
Скролл может заменять время как основную ось анимации. В этом случае прогресс определяется позицией страницы, а не clock-based интервалом.
scrollY$.subscribe((y) => {
const t = Math.min(y / 1000, 1);
element.style.transform = `
translateX(${t * 300}px)
rotate(${t * 360}deg)
`;
});
Такая модель позволяет синхронизировать несколько элементов с одной временной осью, управляемой прокруткой.
Popmotion позволяет объединять физические модели с scroll input. Это создаёт эффект инерционного следования за прокруткой.
import { tween } from 'popmotion';
let current = 0;
scrollY$.subscribe((y) => {
tween({
from: current,
to: y,
duration: 300
}).start((v) => {
current = v;
element.style.transform = `translateY(${v}px)`;
});
});
Такой подход создаёт задержку реакции, визуально отделяя пользовательское действие от результата.
Появление элементов при прокрутке строится на пороговых значениях viewport.
scrollY$.subscribe((y) => {
const trigger = 600;
const opacity = y > trigger ? 1 : 0;
const offset = y > trigger ? 0 : 40;
element.style.opacity = opacity;
element.style.transform = `translateY(${offset}px)`;
});
Popmotion позволяет заменить условную логику на непрерывные функции интерполяции, уменьшая дискретность переходов.
Scroll сам по себе не учитывает размеры viewport. Для адаптивного поведения вводятся относительные коэффициенты:
const vh = window.innerHeight;
const progress = transform([
[0, vh, 0],
[vh, vh * 2, 1]
]);
Это создаёт поведение, зависящее от экрана устройства, а не от абсолютных пикселей.
Scroll часто комбинируется с pointer, hover или drag input. Popmotion позволяет объединять потоки в единый контроллер состояния.
import { merge } from 'popmotion';
const combined$ = merge(scrollY$, pointerX$);
combined$.subscribe(([scroll, pointer]) => {
element.style.transform = `
translate(${pointer}px, ${scroll}px)
`;
});
Такая композиция создаёт многомерное управление интерфейсом.
Scroll-события могут генерироваться с высокой частотой. Для снижения нагрузки применяется throttling и requestAnimationFrame синхронизация.
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
const y = window.scrollY;
element.style.transform = `translateY(${y}px)`;
ticking = false;
});
ticking = true;
}
});
Popmotion абстрагирует подобные механизмы через внутренние scheduling стратегии, снижая необходимость ручного контроля.
При расширенном применении скролл перестаёт быть событием и становится координатной системой. Элементы интерфейса размещаются не в фиксированном DOM-положении, а в зависимости от scroll-space.
Каждый компонент получает функцию:
f(scrollY) → visual state
и обновляется непрерывно в зависимости от глобального положения страницы.
Popmotion позволяет строить цепочки преобразований, где scroll проходит через несколько стадий:
scrollY → normalize → clamp → spring → render
Каждый этап отвечает за отдельную часть поведения: нормализацию диапазона, ограничение значений, физическое сглаживание и отрисовку.
const pipeline = transform([
[0, 2000, 0, 1]
]);
Такая композиция упрощает масштабирование сложных интерфейсов.
Помимо позиции, используется производная величина — скорость изменения scroll.
let lastY = 0;
let velocity = 0;
scrollY$.subscribe((y) => {
velocity = y - lastY;
lastY = y;
element.style.filter = `blur(${Math.abs(velocity) * 0.02}px)`;
});
Это создаёт эффекты размытия, смещения или динамического искажения, зависящие от темпа прокрутки.
При построении сложных интерфейсов scroll используется как глобальный таймлайн, к которому привязываются сцены.
Каждая сцена определяет:
const scene1 = transform([[0, 500, 0, 1]]);
const scene2 = transform([[500, 1000, 0, 1]]);
Это позволяет разбивать длинные страницы на независимые анимационные блоки.
Глубина интерфейса формируется через независимые скорости движения слоёв, создавая иллюзию объёма.
Каждый слой получает собственный коэффициент scroll influence:
const layers = [
{ el: layer1, speed: 0.2 },
{ el: layer2, speed: 0.5 },
{ el: layer3, speed: 1.0 }
];
scrollY$.subscribe((y) => {
layers.forEach(({ el, speed }) => {
el.style.transform = `translateY(${y * speed}px)`;
});
});
Popmotion используется как координирующий слой для таких зависимостей, обеспечивая единый поток значений.