Работа со скроллом в связке с Popmotion строится вокруг идеи реактивного преобразования значения прокрутки в анимационные параметры. Скролл перестаёт быть просто событием интерфейса и превращается в непрерывный источник данных, который можно масштабировать, интерполировать и связывать с любыми визуальными свойствами.
Основой является отслеживание scrollY через слушатель
событий и передача этого значения в систему управления значениями
Popmotion.
import { value, listen, transform } from 'popmotion';
const scrollY = value(0, (v) => {
console.log('Scroll position:', v);
});
listen(window, 'scroll').start(() => {
scrollY.set(window.scrollY);
});
Здесь создаётся реактивное значение, которое обновляется при каждом событии прокрутки. Такой подход позволяет отделить источник данных от логики анимации.
Часто требуется нормализовать прокрутку в диапазон от 0 до 1 для удобного управления анимациями.
import { transform } from 'popmotion';
const clamp = transform.clamp(0, 1);
const progress = transform.pipe(
v => v / 1000,
clamp
);
listen(window, 'scroll').start(() => {
const value = progress(window.scrollY);
console.log(value);
});
Нормализация позволяет использовать скролл как единый управляющий параметр для множества анимаций.
Основная сила подхода проявляется при привязке прокрутки к свойствам
DOM-элементов через styler.
import { styler } from 'popmotion';
const box = document.querySelector('.box');
const boxStyler = styler(box);
listen(window, 'scroll').start(() => {
const y = window.scrollY;
boxStyler.set({
y: y * 0.5
});
});
Так реализуется простейший параллакс-эффект, где элемент движется медленнее, чем прокрутка страницы.
Интерполяция позволяет преобразовывать диапазон скролла в любые визуальные значения: прозрачность, масштаб, поворот.
import { transform } from 'popmotion';
const opacityMap = transform.interpolate(
[0, 300, 600],
[1, 0.5, 0]
);
listen(window, 'scroll').start(() => {
const opacity = opacityMap(window.scrollY);
boxStyler.set({ opacity });
});
Такой подход позволяет строить сложные сценарии исчезновения или появления элементов в зависимости от положения прокрутки.
При работе с несколькими слоями создаётся эффект глубины за счёт различных коэффициентов движения.
const background = styler(document.querySelector('.bg'));
const midground = styler(document.querySelector('.mid'));
const foreground = styler(document.querySelector('.front'));
listen(window, 'scroll').start(() => {
const y = window.scrollY;
background.set({ y: y * 0.2 });
midground.set({ y: y * 0.5 });
foreground.set({ y: y * 0.8 });
});
Различные коэффициенты создают ощущение пространственного разделения элементов сцены.
Прямое использование scrollY часто даёт резкие
обновления. Для плавного движения применяется инерционная модель.
import { value, inertia } from 'popmotion';
const smoothScroll = value(0, (v) => {
boxStyler.set({ y: v });
});
listen(window, 'scroll').start(() => {
inertia({
from: smoothScroll.get(),
velocity: window.scrollY - smoothScroll.get(),
power: 0.8,
timeConstant: 300
}).start(smoothScroll);
});
Инерция позволяет сгладить резкие скачки и создать ощущение физического движения.
Часто требуется вычислять прогресс внутри конкретного блока страницы.
const section = document.querySelector('.section');
const sectionHeight = section.offsetHeight;
listen(window, 'scroll').start(() => {
const rect = section.getBoundingClientRect();
const progress = 1 - rect.top / sectionHeight;
boxStyler.set({
scale: progress
});
});
Такой подход позволяет создавать анимации, которые активируются только внутри определённого диапазона прокрутки.
transform.pipe позволяет строить цепочки преобразований,
объединяя несколько логических операций.
const scrollTransform = transform.pipe(
v => v - 200,
transform.clamp(0, 500),
v => v / 500
);
listen(window, 'scroll').start(() => {
const progress = scrollTransform(window.scrollY);
boxStyler.set({
rotate: progress * 180,
opacity: 1 - progress
});
});
Пайплайн делает логику более декларативной и удобной для масштабирования.
Один поток данных может управлять множеством независимых эффектов.
listen(window, 'scroll').start(() => {
const y = window.scrollY;
boxStyler.set({
y: y * 0.3,
scale: 1 + y / 2000
});
headerStyler.set({
opacity: Math.max(0, 1 - y / 300)
});
imageStyler.set({
rotate: y * 0.1
});
});
Такой подход централизует управление визуальной динамикой интерфейса.
Прокрутка может генерировать большое количество событий, что требует
ограничения частоты обновлений через
requestAnimationFrame.
let latestY = 0;
let ticking = false;
listen(window, 'scroll').start(() => {
latestY = window.scrollY;
if (!ticking) {
ticking = true;
requestAnimationFrame(() => {
boxStyler.set({ y: latestY });
ticking = false;
});
}
});
Это предотвращает избыточные пересчёты и снижает нагрузку на рендеринг.
Скролл может выступать триггером для запуска физически-основанных анимаций.
import { spring } from 'popmotion';
listen(window, 'scroll').start(() => {
const target = window.scrollY / 2;
spring({
from: boxStyler.get('y'),
to: target,
stiffness: 120,
damping: 20
}).start(boxStyler.set);
});
Так создаётся эффект “догоняющего” движения элементов за прокруткой.
Скролл может взаимодействовать с другими источниками данных, такими как pointer или drag-события, формируя единый управляющий слой анимации.
let dragOffset = 0;
listen(window, 'scroll').start(() => {
const y = window.scrollY + dragOffset;
boxStyler.set({
y
});
});
Такая архитектура позволяет объединять различные типы пользовательского ввода в одну систему координат.
Разные участки страницы могут иметь собственные диапазоны реакции на прокрутку.
const ranges = [
{ start: 0, end: 300, output: [0, 1] },
{ start: 300, end: 800, output: [1, 0] }
];
listen(window, 'scroll').start(() => {
const y = window.scrollY;
const segment = ranges.find(r => y >= r.start && y < r.end);
if (segment) {
const progress =
(y - segment.start) / (segment.end - segment.start);
boxStyler.set({
opacity: transform.interpolate(
[0, 1],
segment.output
)(progress)
});
}
});
Так создаются сложные сцены с последовательной сменой визуальных состояний.
Прокрутка может выступать аналогом временной оси, где координата страницы заменяет время анимации.
const timeline = (y) => {
return y / 1000;
};
listen(window, 'scroll').start(() => {
const t = timeline(window.scrollY);
boxStyler.set({
y: t * 500,
rotate: t * 360,
scale: 1 + t * 0.5
});
});
Такой подход позволяет строить интерфейсы, где движение страницы напрямую определяет состояние сцены.