Создание собственных функций плавности

Функция плавности (easing) определяет характер изменения значения анимации во времени. Вместо линейного перехода от начального состояния к конечному используется математическая кривая, задающая ускорение и замедление. В Velocity.js это один из ключевых механизмов управления ощущением анимации.

В основе любой easing-функции лежит отображение диапазона времени [0, 1] → [0, 1], где входное значение — прогресс анимации, а выходное — скорректированное значение, влияющее на интерполяцию свойств.


Архитектура easing в Velocity.js

Velocity.js поддерживает несколько уровней определения функций плавности:

  • встроенные строки-идентификаторы ("ease", "ease-in", "ease-out")
  • расширенные функции из набора Penner easing
  • пользовательские функции
  • CSS cubic-bezier выражения (в зависимости от режима использования)

Внутренне Velocity нормализует easing до функции вида:

function easing(t) {
    return value;
}

где:

  • t — прогресс анимации от 0 до 1
  • value — модифицированный прогресс

Базовая модель пользовательской функции

Пользовательская функция плавности в Velocity.js представляет собой чистую функцию, принимающую числовой параметр и возвращающую числовое значение.

function customEasing(progress) {
    return progress;
}

При таком определении поведение эквивалентно линейной анимации.

Любые модификации формы кривой достигаются изменением зависимости между входом и выходом.


Регистрация пользовательских easing-функций

Velocity.js позволяет расширять набор easing-функций через глобальный объект:

Velocity.Easings.myEasing = function (t) {
    return t;
};

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

Velocity(element, { opacity: 1 }, { duration: 1000, easing: "myEasing" });

Аналогичная регистрация в jQuery-совместимом синтаксисе:

$.Velocity.Easings.myEasing = function (t) {
    return t;
};

Линейные преобразования как основа кастомизации

Простейшие модификации создаются через геометрические преобразования функции t.

Ускорение (ease-in-подобное поведение)

Velocity.Easings.accelerate = function (t) {
    return t * t;
};

Поведение:

  • медленный старт
  • ускорение к концу

Замедление (ease-out-подобное поведение)

Velocity.Easings.decelerate = function (t) {
    return 1 - Math.pow(1 - t, 2);
};

Поведение:

  • быстрый старт
  • плавное завершение

Комбинированная кривая (ease-in-out)

Velocity.Easings.smooth = function (t) {
    return t < 0.5
        ? 2 * t * t
        : 1 - Math.pow(-2 * t + 2, 2) / 2;
};

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


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

Полиномы позволяют задавать более сложные формы движения.

Кубическая кривая

Velocity.Easings.cubicIn = function (t) {
    return t * t * t;
};

Обратная кубическая кривая

Velocity.Easings.cubicOut = function (t) {
    return 1 - Math.pow(1 - t, 3);
};

Чем выше степень полинома, тем более выраженной становится нелинейность движения.


Эластичные функции (spring / elastic behavior)

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

Velocity.Easings.elastic = function (t) {
    return Math.sin(-13 * (Math.PI / 2) * (t + 1)) * Math.pow(2, -10 * t) + 1;
};

Характерные особенности:

  • выход за пределы диапазона [0, 1]
  • затухающие колебания
  • эффект пружины

Bounce-эффект

Bounce-эффект имитирует падение и отскок объекта. Реализация обычно строится через кусочную функцию.

Velocity.Easings.bounce = function (t) {
    if (t < 1 / 2.75) {
        return 7.5625 * t * t;
    } else if (t < 2 / 2.75) {
        return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
    } else if (t < 2.5 / 2.75) {
        return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
    } else {
        return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
    }
};

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


Использование математических констант и нормализации

Для корректной работы easing-функции важно соблюдать нормализацию входного значения:

  • t < 0 — приводится к 0
  • t > 1 — приводится к 1

Иногда это делается внутри функции:

function clamp(t) {
    return Math.max(0, Math.min(1, t));
}

Применение:

Velocity.Easings.safeEase = function (t) {
    t = Math.max(0, Math.min(1, t));
    return t * t;
};

Комбинирование функций плавности

Сложные кривые могут быть построены через композицию функций.

Velocity.Easings.composite = function (t) {
    function easeIn(x) {
        return x * x;
    }

    function easeOut(x) {
        return 1 - Math.pow(1 - x, 2);
    }

    return t < 0.5
        ? easeIn(t * 2) / 2
        : 0.5 + easeOut((t - 0.5) * 2) / 2;
};

Такой подход позволяет разносить поведение по сегментам времени.


Генерация easing через параметры

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

function createPowerEase(power) {
    return function (t) {
        return Math.pow(t, power);
    };
}

Velocity.Easings.easeInQuad = createPowerEase(2);
Velocity.Easings.easeInQuart = createPowerEase(4);

Подобный подход снижает дублирование кода и упрощает масштабирование набора анимаций.


Использование cubic-bezier как источника кастомных кривых

Хотя Velocity.js не всегда напрямую интерпретирует CSS cubic-bezier, математическая модель может быть реализована вручную:

Velocity.Easings.cubicBezier = function (t) {
    const c1 = 0.42;
    const c2 = 0.0;
    const c3 = 0.58;
    const c4 = 1.0;

    return (
        Math.pow(1 - t, 3) * 0 +
        3 * Math.pow(1 - t, 2) * t * c1 +
        3 * (1 - t) * Math.pow(t, 2) * c3 +
        Math.pow(t, 3) * 1
    );
};

Такие функции позволяют приближать поведение CSS-анимаций внутри JavaScript-движка.


Производительность пользовательских easing-функций

Поскольку easing вызывается на каждом кадре анимации, важна минимизация вычислений:

  • избегание лишних тригонометрических операций
  • отказ от аллокаций объектов
  • использование локальных переменных вместо глобальных обращений

Оптимизированный вариант:

const sin = Math.sin;
const pow = Math.pow;

Velocity.Easings.fastElastic = function (t) {
    return sin(-10 * t) * pow(2, -5 * t) + 1;
};

Поведение выходящих за пределы диапазона значений

Некоторые easing-функции возвращают значения меньше 0 или больше 1. Это используется для:

  • эффекта overshoot
  • пружинных анимаций
  • инерционного движения

Пример:

Velocity.Easings.overshoot = function (t) {
    const s = 1.70158;
    return t * t * ((s + 1) * t - s);
};

Такие функции расширяют визуальную динамику интерфейса.


Интеграция пользовательских easing в цепочки анимаций

В Velocity.js easing применяется на уровне каждого вызова анимации:

Velocity(element, {
    translateX: "200px",
    opacity: 0
}, {
    duration: 800,
    easing: "smooth"
});

При последовательных анимациях каждая фаза может использовать собственную кривую:

Velocity(element, "fadeIn", { easing: "cubicIn" })
    .then(() => Velocity(element, "fadeOut", { easing: "bounce" }));

Абстракция набора easing-функций

При масштабных проектах набор функций плавности часто структурируется как библиотека:

const Easings = {
    linear: function (t) { return t; },
    smooth: function (t) {
        return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
    },
    bounce: function (t) {
        if (t < 0.5) return 7.5625 * t * t;
        return 1 - 7.5625 * (1 - t) * (1 - t);
    }
};

Object.assign(Velocity.Easings, Easings);

Такая структура упрощает повторное использование и тестирование поведения анимаций.