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

Функции плавности (easing functions) определяют характер изменения значения во времени — ускорение, замедление, инерцию и другие физически правдоподобные эффекты. В библиотеке Popmotion они представлены как обычные функции, принимающие прогресс t в диапазоне [0, 1] и возвращающие преобразованное значение.

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


Базовая сигнатура функции плавности

Любая функция плавности в Popmotion имеет вид:

const easing = (t) => t

Где:

  • t — нормализованное время (от 0 до 1)
  • возвращаемое значение — преобразованное значение прогресса

Последовательное комбинирование (function composition)

Один из ключевых подходов — передача результата одной функции в другую.

const easeIn = (t) => t * t
const easeOut = (t) => 1 - Math.pow(1 - t, 2)

const combined = (t) => easeOut(easeIn(t))

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

  • первая функция трансформирует временной прогресс
  • вторая функция применяет дополнительную модификацию
  • итоговая кривая становится более сложной

Использование pipe для композиции

Popmotion предоставляет утилиту pipe, которая упрощает цепочку функций:

import { pipe } from 'popmotion'

const easeIn = (t) => t * t
const easeOut = (t) => 1 - Math.pow(1 - t, 2)

const combined = pipe(easeIn, easeOut)

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

  • читаемость
  • декларативность
  • удобство масштабирования

Разделение диапазона (segmenting)

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

const easeIn = (t) => t * t
const easeOut = (t) => 1 - Math.pow(1 - t, 2)

const segmented = (t) => {
  if (t < 0.5) {
    return 0.5 * easeIn(t * 2)
  } else {
    return 0.5 + 0.5 * easeOut((t - 0.5) * 2)
  }
}

Идея:

  • первая половина — ускорение
  • вторая половина — замедление
  • масштабирование сохраняет диапазон [0, 1]

Интерполяция между функциями

Popmotion позволяет интерполировать не только значения, но и сами функции.

import { mix } from 'popmotion'

const easeIn = (t) => t * t
const easeOut = (t) => 1 - Math.pow(1 - t, 2)

const blend = (t) => {
  const factor = t
  return mix(easeIn(t), easeOut(t), factor)
}

Результат:

  • плавный переход от одной кривой к другой
  • динамическое изменение характера анимации

Использование easing внутри tween

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

import { tween } from 'popmotion'

tween({
  from: 0,
  to: 100,
  duration: 1000,
  ease: combined
}).start(console.log)

Создание кастомных кривых через массивы

Popmotion поддерживает сложные кривые через keyframes и easing-массивы:

import { keyframes, easing } from 'popmotion'

keyframes({
  values: [0, 50, 100],
  ease: [easing.easeIn, easing.easeOut],
  duration: 1000
}).start(console.log)

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

  • каждая пара значений имеет свою функцию плавности
  • создаётся сегментированная анимация без ручной логики

Инверсия функций

Комбинирование часто включает инверсию:

const invert = (fn) => (t) => 1 - fn(1 - t)

const easeIn = (t) => t * t
const easeOut = invert(easeIn)

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

  • не нужно писать отдельную функцию
  • симметричное поведение

Усиление и ослабление эффекта

Можно изменять интенсивность функции:

const amplify = (fn, power) => (t) => Math.pow(fn(t), power)

const strongEase = amplify((t) => t, 3)

Или сглаживать:

const soften = (fn, factor) => (t) => fn(t) * factor + t * (1 - factor)

Комбинирование с физическими моделями

Popmotion позволяет сочетать easing с физическими анимациями (spring, decay).

import { spring } from 'popmotion'

spring({
  from: 0,
  to: 100,
  stiffness: 200,
  damping: 15
}).start(console.log)

Комбинирование происходит на уровне управления прогрессом:

const customEase = (t) => t * t * (3 - 2 * t) // smoothstep

Нелинейные трансформации времени

Можно трансформировать само время:

const warpTime = (fn) => (t) => fn(t)

const bounceTime = (t) => Math.abs(Math.sin(6.28 * t) * (1 - t))

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

const complex = pipe(bounceTime, (t) => t * t)

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

Popmotion поддерживает cubic-bezier:

import { cubicBezier } from 'popmotion'

const ease = cubicBezier(0.42, 0, 0.58, 1)

Комбинирование:

const combinedBezier = pipe(
  cubicBezier(0.42, 0, 1, 1),
  cubicBezier(0, 0, 0.58, 1)
)

Практические паттерны

1. Ease-in + overshoot + settle

const overshoot = (t) => t + Math.sin(t * Math.PI) * 0.2

const pattern = pipe(
  (t) => t * t,
  overshoot,
  (t) => Math.min(t, 1)
)

2. Медленный старт, быстрый центр, плавный конец

const custom = (t) => {
  if (t < 0.3) return t * t
  if (t < 0.7) return t
  return 1 - Math.pow(1 - t, 2)
}

Оптимизация и производительность

  • избегать избыточных вложенных функций
  • кэшировать сложные функции
  • использовать pipe вместо глубоких вложений
  • минимизировать вычисления внутри анимационного цикла

Читаемость и масштабируемость

Комбинированные функции лучше оформлять как независимые модули:

export const easeIn = (t) => t * t
export const easeOut = (t) => 1 - Math.pow(1 - t, 2)

export const easeInOut = pipe(easeIn, easeOut)

Типичные ошибки

  • отсутствие нормализации диапазона [0, 1]
  • неправильное масштабирование сегментов
  • чрезмерное усложнение кривых
  • потеря монотонности (анимация “дергается”)

Расширение через высшие функции

Функции высшего порядка позволяют строить универсальные композиции:

const chain = (...fns) => (t) =>
  fns.reduce((acc, fn) => fn(acc), t)

const easing = chain(
  (t) => t * t,
  (t) => Math.sin(t * Math.PI / 2)
)

Комбинирование как инструмент выразительности

Сложные easing-функции позволяют:

  • имитировать физику
  • создавать уникальный UI-стиль
  • управлять восприятием скорости
  • усиливать интерактивность интерфейса

Разнообразие подходов — от простой композиции до сегментации и интерполяции — делает Popmotion мощным инструментом для тонкой настройки анимаций.