Создание кастомных easing функций

Кастомные функции плавности (easing) в mo.js представляют собой механизм управления временным распределением анимации через математическое преобразование нормализованного времени. В базовом виде easing принимает параметр t в диапазоне от 0 до 1 и возвращает преобразованное значение, определяющее, как именно изменяется анимационное свойство во времени.

Внутри mo.js easing используется как функция вида:

function easing(t) {
  return value;
}

где:

  • t — нормализованное время анимации (0 — старт, 1 — завершение)
  • возвращаемое значение — коэффициент прогресса свойства

Система анимации mo.js применяет easing ко всем tween-значениям, включая трансформации, opacity, масштабирование, перемещение и параметры эффектов.

Библиотека предоставляет набор встроенных кривых: линейные, квадратичные, кубические, экспоненциальные, эластичные и “bounce”-подобные функции. Однако архитектура позволяет подключать полностью произвольные алгоритмы интерполяции.

Функциональная форма кастомного easing

Базовый вариант кастомной функции строится напрямую:

const customEasing = function (t) {
  return t;
};

Любая функция, удовлетворяющая контракту t ∈ [0,1] → R, может использоваться как easing. При этом результат не обязан оставаться в диапазоне 0–1, что позволяет создавать overshoot-эффекты и инерционные колебания.

Пример нелинейного ускорения:

const customEase = function (t) {
  return t * t * (3 - 2 * t);
};

Такая функция формирует плавное ускорение с замедлением на концах, аналогичное smoothstep-поведению.

Подключение кастомного easing в mo.js

Кастомные функции могут передаваться напрямую в параметры tween-объектов:

import mojs from 'mo-js';

const animation = new mojs.Html({
  el: '.box',
  x: { 0: 300 },
  duration: 1000,
  easing: function (t) {
    return Math.pow(t, 3);
  }
});

Здесь используется кубическая кривая ускорения, усиливающая динамику движения ближе к завершению анимации.

Регистрация easing в пространстве mojs.easing

Для повторного использования функции удобно добавлять её в глобальный реестр easing:

mojs.easing.customSoft = function (t) {
  return t * t * (3 - t);
};

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

const anim = new mojs.Html({
  el: '.circle',
  scale: { 0: 1 },
  easing: 'customSoft'
});

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

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

Одним из наиболее распространённых способов создания кастомной плавности является кубическая кривая Безье. В mo.js она используется через генерацию функции из контрольных точек.

Общий вид:

const cubicBezier = function (p0, p1, p2, p3) {
  return function (t) {
    // вычисление значения кривой
  };
};

В реальных сценариях чаще применяется готовая реализация через интерполяцию:

const easeInOut = mojs.easing.path('M0,0 C0.42,0 0.58,1 1,1');

Такой подход позволяет задавать поведение easing через SVG-подобное описание кривой.

Path-based easing

mo.js поддерживает создание easing-функций на основе SVG path. Это один из наиболее гибких механизмов, позволяющий визуально проектировать кривые.

const customPathEase = mojs.easing.path('M0 0 C0.2 0.8 0.2 1 1 1');

Функция интерпретирует путь как кривую зависимости времени от прогресса.

Использование в анимации:

const anim = new mojs.Html({
  el: '.square',
  x: { 0: 400 },
  duration: 1200,
  easing: customPathEase
});

Создание инерционных и “физических” easing

Кастомные easing-функции часто применяются для имитации физического поведения: упругости, трения, инерции.

Эластичное движение

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

Функция создаёт эффект пружины, где объект выходит за пределы целевого значения и возвращается обратно.

Bounce-поведение

function bounceOut(t) {
  const n1 = 7.5625;
  const d1 = 2.75;

  if (t < 1 / d1) {
    return n1 * t * t;
  } else if (t < 2 / d1) {
    t -= 1.5 / d1;
    return n1 * t * t + 0.75;
  } else if (t < 2.5 / d1) {
    t -= 2.25 / d1;
    return n1 * t * t + 0.9375;
  } else {
    t -= 2.625 / d1;
    return n1 * t * t + 0.984375;
  }
}

Такие функции применяются для анимаций, имитирующих падение объектов и отскоки.

Комбинирование easing-функций

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

function combineEase(t) {
  const easeIn = t * t;
  const easeOut = 1 - Math.pow(1 - t, 2);

  return t < 0.5
    ? easeIn * 2
    : easeOut * 2 - 1;
}

Такой подход позволяет вручную формировать easing-in-out без использования встроенных кривых.

Нормализация и ограничения значений

В отличие от линейных интерполяций, easing-функции не обязаны возвращать строго ограниченные значения. Однако в контексте mo.js важно учитывать поведение трансформаций:

  • значения > 1 могут создавать overshoot
  • значения < 0 могут уводить объект за начальную позицию
  • некорректные диапазоны приводят к визуальным артефактам

Для стабилизации используется clamp:

function safeEase(t) {
  const v = t * t * (3 - t);
  return Math.max(0, Math.min(1, v));
}

Производительность кастомных easing

Easing-функции вызываются на каждом шаге анимации, часто десятки или сотни раз в кадре при сложных сценах. Это делает критичным их вычислительную стоимость.

Оптимизации включают:

  • исключение тригонометрии в массовых анимациях
  • предварительное вычисление lookup-таблиц
  • использование простых полиномиальных функций
  • минимизацию ветвлений

Пример табличного подхода:

const table = new Array(1000).fill(0).map((_, i) => {
  const t = i / 999;
  return t * t;
});

function tableEase(t) {
  return table[Math.floor(t * 999)];
}

Интеграция easing в timelines mo.js

В сложных сценах easing применяется не только к отдельным tween-объектам, но и к временным линиям.

const timeline = new mojs.Timeline({
  children: [
    new mojs.Html({
      el: '.a',
      x: { 0: 200 },
      easing: function (t) {
        return Math.pow(t, 2);
      }
    }),
    new mojs.Html({
      el: '.b',
      scale: { 0: 1 },
      easing: 'elastic.out'
    })
  ]
});

Сочетание разных easing внутри одной timeline позволяет создавать сложные синхронизированные движения.

Отладка кастомных easing-кривых

При разработке сложных функций часто требуется визуальная проверка поведения. Для этого используется вывод значений функции на график:

const points = [];
for (let i = 0; i <= 100; i++) {
  const t = i / 100;
  points.push({ t, v: customEase(t) });
}

Анализ распределения значений позволяет выявлять резкие скачки, неестественные ускорения и выходы за диапазон.

Типовые паттерны кастомных easing

Часто используемые структуры:

Мягкое ускорение с инерцией

t * t * (2 - t)

Глубокое замедление

1 - Math.pow(1 - t, 4)

Пульсирующая динамика

function pulse(t) {
  return t + 0.1 * Math.sin(t * Math.PI * 4);
}

Каждый паттерн формирует характер движения интерфейса, влияя на восприятие скорости, веса и физической достоверности объектов в сцене.