RequestAnimationFrame

requestAnimationFrame — это метод браузера, который позволяет выполнять анимации плавно и эффективно, синхронизируя их с частотой обновления экрана. В Interact.js он используется для оптимизации визуальных изменений элементов при перетаскивании, масштабировании или вращении, чтобы исключить дрожание и лаги.

Метод принимает функцию обратного вызова, которая вызывается перед следующей перерисовкой браузера:

function animate(time) {
    // действия с элементом
    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

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


Интеграция с Interact.js

Interact.js предоставляет события drag, resize и gesture. Все визуальные обновления элементов должны выполняться через requestAnimationFrame для плавности:

interact('.draggable')
    .draggable({
        listeners: {
            move(event) {
                requestAnimationFrame(() => {
                    const target = event.target;
                    const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
                    const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

                    target.style.transform = `translate(${x}px, ${y}px)`;
                    target.setAttribute('data-x', x);
                    target.setAttribute('data-y', y);
                });
            }
        }
    });

Ключевые моменты:

  • Перемещение происходит только внутри функции requestAnimationFrame.
  • Использование атрибутов data-x и data-y обеспечивает корректное накопление смещения.
  • Изменение CSS-свойства transform вместо top/left уменьшает перерисовку и повышает производительность.

Оптимизация сложных анимаций

При работе с несколькими элементами или интенсивными изменениями рекомендуется использовать один общий цикл requestAnimationFrame вместо вызова для каждого события. Это уменьшает нагрузку на браузер и предотвращает коллизии:

const elements = new Map();

interact('.draggable').draggable({
    listeners: {
        move(event) {
            const target = event.target;
            const prev = elements.get(target) || { x: 0, y: 0 };
            prev.x += event.dx;
            prev.y += event.dy;
            elements.set(target, prev);
        }
    }
});

function animate() {
    elements.forEach((pos, target) => {
        target.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
    });
    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Преимущества:

  • Один вызов requestAnimationFrame обновляет все элементы.
  • Исключается множественное создание анимационных кадров.
  • Легко добавлять новые элементы в общий цикл.

Использование временных дельт для плавной анимации

requestAnimationFrame предоставляет timestamp, который можно использовать для расчета временной дельты между кадрами. Это особенно полезно для анимаций с постоянной скоростью независимо от частоты обновления экрана:

let lastTime = 0;

function animate(time) {
    const delta = time - lastTime;
    lastTime = time;

    elements.forEach((pos, target) => {
        const speed = 0.5; // пиксели на миллисекунду
        pos.x += pos.dx * (delta * speed);
        pos.y += pos.dy * (delta * speed);

        target.style.transform = `translate(${pos.x}px, ${pos.y}px)`;
    });

    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

Особенности:

  • Анимация становится независимой от FPS.
  • Позволяет реализовать плавное ускорение и замедление.
  • Удобно для сложных эффектов drag-and-drop и инерции.

Инерция и requestAnimationFrame в Interact.js

Interact.js поддерживает физически реалистичную инерцию через опцию inertia. Для кастомных эффектов инерции часто используется requestAnimationFrame:

let velocity = { x: 0, y: 0 };
let isMoving = false;

function applyInertia(target) {
    if (!isMoving) return;

    velocity.x *= 0.95;
    velocity.y *= 0.95;

    const x = (parseFloat(target.getAttribute('data-x')) || 0) + velocity.x;
    const y = (parseFloat(target.getAttribute('data-y')) || 0) + velocity.y;

    target.style.transform = `translate(${x}px, ${y}px)`;
    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);

    if (Math.abs(velocity.x) > 0.1 || Math.abs(velocity.y) > 0.1) {
        requestAnimationFrame(() => applyInertia(target));
    } else {
        isMoving = false;
    }
}

Принципы работы:

  • Используется постепенное уменьшение скорости (velocity) для эффекта затухания.
  • Функция рекурсивно вызывает requestAnimationFrame до полного прекращения движения.
  • Эффект инерции синхронизирован с перерисовкой экрана для максимальной плавности.

Комбинирование с CSS-трансформациями и Interact.js

requestAnimationFrame идеально сочетается с CSS-свойствами transform и opacity. Это позволяет реализовать сложные эффекты drag, scale и rotate без значительной нагрузки на браузер. Пример комбинированного перемещения и масштабирования:

interact('.box').draggable({
    listeners: {
        move(event) {
            requestAnimationFrame(() => {
                const target = event.target;
                const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
                const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
                const scale = 1 + 0.001 * (x + y);

                target.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
                target.setAttribute('data-x', x);
                target.setAttribute('data-y', y);
            });
        }
    }
});

Выводы по использованию requestAnimationFrame:

  • Обеспечивает синхронизацию с частотой обновления экрана.
  • Минимизирует лаги и дрожание при взаимодействиях с элементами.
  • Позволяет реализовать инерцию, ускорение и замедление.
  • Поддерживает одновременную обработку нескольких элементов без перегрузки браузера.
  • Рекомендуется использовать совместно с CSS-трансформациями для производительной анимации.