Scrolling элементов

Работа со скроллом в связке с 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 });
});

Различные коэффициенты создают ощущение пространственного разделения элементов сцены.

Сглаживание скролла через inertia

Прямое использование 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 pipeline для сложных эффектов

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;
    });
  }
});

Это предотвращает избыточные пересчёты и снижает нагрузку на рендеринг.

Связь scroll-поведения с физическими анимациями

Скролл может выступать триггером для запуска физически-основанных анимаций.

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);
});

Так создаётся эффект “догоняющего” движения элементов за прокруткой.

Комбинация scroll и gesture-driven логики

Скролл может взаимодействовать с другими источниками данных, такими как 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)
    });
  }
});

Так создаются сложные сцены с последовательной сменой визуальных состояний.

Использование scroll как временной шкалы

Прокрутка может выступать аналогом временной оси, где координата страницы заменяет время анимации.

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
  });
});

Такой подход позволяет строить интерфейсы, где движение страницы напрямую определяет состояние сцены.